1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
5 * Copyright (C) 2009, 2010 Igalia S.L.
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Library General Public
9 * License as published by the Free Software Foundation; either
10 * version 2 of the License, or (at your option) any later version.
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Library General Public License for more details.
17 * You should have received a copy of the GNU Library General Public License
18 * along with this library; see the file COPYING.LIB. If not, write to
19 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
20 * Boston, MA 02110-1301, USA.
24 * - Need to hook the feature in the sync SoupSession.
34 #include "soup-cache.h"
35 #include "soup-body-input-stream.h"
36 #include "soup-cache-input-stream.h"
37 #include "soup-cache-private.h"
38 #include "soup-content-processor.h"
39 #include "soup-message-private.h"
41 #include "soup-message-private.h"
44 #if ENABLE(TIZEN_TV_SOUP_CACHE_CLEAN_LEAKED_RESOURCES)
45 #include <glib/gstdio.h>
47 #if ENABLE(TIZEN_TV_SOUP_CACHE_OPTIMISE_LOAD_TIME)
56 * @short_description: Caching support
58 * #SoupCache implements a file-based cache for HTTP resources.
61 static SoupSessionFeatureInterface *soup_cache_default_feature_interface;
62 static void soup_cache_session_feature_init (SoupSessionFeatureInterface *feature_interface, gpointer interface_data);
64 static SoupContentProcessorInterface *soup_cache_default_content_processor_interface;
65 static void soup_cache_content_processor_init (SoupContentProcessorInterface *interface, gpointer interface_data);
67 #define DEFAULT_MAX_SIZE 50 * 1024 * 1024
68 #define MAX_ENTRY_DATA_PERCENTAGE 10 /* Percentage of the total size
69 of the cache that can be
70 filled by a single entry */
73 * Version 2: cache is now saved in soup.cache2. Added the version
74 * number to the beginning of the file.
76 * Version 3: added HTTP status code to the cache entries.
78 * Version 4: replaced several types.
79 * - freshness_lifetime,corrected_initial_age,response_time: time_t -> guint32
80 * - status_code: guint -> guint16
81 * - hits: guint -> guint32
83 * Version 5: key is no longer stored on disk as it can be easily
84 * built from the URI. Apart from that some fields in the
85 * SoupCacheEntry have changed:
86 * - entry key is now a uint32 instead of a (char *).
87 * - added uri, used to check for collisions
88 * - removed filename, it's built from the entry key.
90 #define SOUP_CACHE_CURRENT_VERSION 5
92 #define OLD_SOUP_CACHE_FILE "soup.cache"
93 #define SOUP_CACHE_FILE "soup.cache2"
95 #define SOUP_CACHE_HEADERS_FORMAT "{ss}"
96 #if ENABLE(TIZEN_USER_AGENT_CHECK_IN_CACHE)
97 #define SOUP_CACHE_PHEADERS_FORMAT "(sbuuuuuqsa" SOUP_CACHE_HEADERS_FORMAT ")"
99 #define SOUP_CACHE_PHEADERS_FORMAT "(sbuuuuuqa" SOUP_CACHE_HEADERS_FORMAT ")"
101 #define SOUP_CACHE_ENTRIES_FORMAT "(qa" SOUP_CACHE_PHEADERS_FORMAT ")"
103 /* Basically the same format than above except that some strings are
104 prepended with &. This way the GVariant returns a pointer to the
105 data instead of duplicating the string */
106 #define SOUP_CACHE_DECODE_HEADERS_FORMAT "{&s&s}"
109 typedef struct _SoupCacheEntry {
112 guint32 freshness_lifetime;
113 gboolean must_revalidate;
115 guint32 corrected_initial_age;
116 guint32 response_time;
118 gboolean being_validated;
119 SoupMessageHeaders *headers;
121 GCancellable *cancellable;
123 #if ENABLE(TIZEN_USER_AGENT_CHECK_IN_CACHE)
128 struct _SoupCachePrivate {
132 SoupSession *session;
133 SoupCacheType cache_type;
136 guint max_entry_data_size; /* Computed value. Here for performance reasons */
146 #define SOUP_CACHE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SOUP_TYPE_CACHE, SoupCachePrivate))
148 G_DEFINE_TYPE_WITH_CODE (SoupCache, soup_cache, G_TYPE_OBJECT,
149 G_IMPLEMENT_INTERFACE (SOUP_TYPE_SESSION_FEATURE,
150 soup_cache_session_feature_init)
151 G_IMPLEMENT_INTERFACE (SOUP_TYPE_CONTENT_PROCESSOR,
152 soup_cache_content_processor_init))
154 static gboolean soup_cache_entry_remove (SoupCache *cache, SoupCacheEntry *entry, gboolean purge);
155 static void make_room_for_new_entry (SoupCache *cache, guint length_to_add);
156 static gboolean cache_accepts_entries_of_size (SoupCache *cache, guint length_to_add);
159 get_file_from_entry (SoupCache *cache, SoupCacheEntry *entry)
161 char *filename = g_strdup_printf ("%s%s%u", cache->priv->cache_dir,
162 G_DIR_SEPARATOR_S, (guint) entry->key);
163 GFile *file = g_file_new_for_path (filename);
169 static SoupCacheability
170 get_cacheability (SoupCache *cache, SoupMessage *msg)
172 SoupCacheability cacheability;
173 const char *cache_control, *content_type;
174 gboolean has_max_age = FALSE;
176 /* 1. The request method must be cacheable */
177 if (msg->method == SOUP_METHOD_GET)
178 cacheability = SOUP_CACHE_CACHEABLE;
179 else if (msg->method == SOUP_METHOD_HEAD ||
180 msg->method == SOUP_METHOD_TRACE ||
181 msg->method == SOUP_METHOD_CONNECT)
182 return SOUP_CACHE_UNCACHEABLE;
184 return (SOUP_CACHE_UNCACHEABLE | SOUP_CACHE_INVALIDATES);
186 content_type = soup_message_headers_get_content_type (msg->response_headers, NULL);
187 if (content_type && !g_ascii_strcasecmp (content_type, "multipart/x-mixed-replace"))
188 return SOUP_CACHE_UNCACHEABLE;
190 #if ENABLE(TIZEN_TV_NO_CACHE_ABOUT_VIDEO_AND_AUDIO)
191 if (content_type && (!g_ascii_strncasecmp (content_type, "video/", 6) || !g_ascii_strncasecmp (content_type, "audio/", 6)))
192 return SOUP_CACHE_UNCACHEABLE;
195 cache_control = soup_message_headers_get_list (msg->response_headers, "Cache-Control");
196 if (cache_control && *cache_control) {
198 SoupCachePrivate *priv = SOUP_CACHE_GET_PRIVATE (cache);
200 hash = soup_header_parse_param_list (cache_control);
202 /* Shared caches MUST NOT store private resources */
203 if (priv->cache_type == SOUP_CACHE_SHARED) {
204 if (g_hash_table_lookup_extended (hash, "private", NULL, NULL)) {
205 soup_header_free_param_list (hash);
206 return SOUP_CACHE_UNCACHEABLE;
210 /* 2. The 'no-store' cache directive does not appear in the
213 if (g_hash_table_lookup_extended (hash, "no-store", NULL, NULL)) {
214 soup_header_free_param_list (hash);
215 return SOUP_CACHE_UNCACHEABLE;
218 if (g_hash_table_lookup_extended (hash, "max-age", NULL, NULL))
221 /* This does not appear in section 2.1, but I think it makes
222 * sense to check it too?
224 if (g_hash_table_lookup_extended (hash, "no-cache", NULL, NULL)) {
225 soup_header_free_param_list (hash);
226 return SOUP_CACHE_UNCACHEABLE;
229 soup_header_free_param_list (hash);
233 if ((soup_message_get_uri (msg))->query &&
234 !soup_message_headers_get_one (msg->response_headers, "Expires") &&
236 return SOUP_CACHE_UNCACHEABLE;
238 switch (msg->status_code) {
239 case SOUP_STATUS_PARTIAL_CONTENT:
240 /* We don't cache partial responses, but they only
241 * invalidate cached full responses if the headers
244 cacheability = SOUP_CACHE_UNCACHEABLE;
247 case SOUP_STATUS_NOT_MODIFIED:
248 /* A 304 response validates an existing cache entry */
249 cacheability = SOUP_CACHE_VALIDATES;
252 case SOUP_STATUS_MULTIPLE_CHOICES:
253 case SOUP_STATUS_MOVED_PERMANENTLY:
254 case SOUP_STATUS_GONE:
255 /* FIXME: cacheable unless indicated otherwise */
256 cacheability = SOUP_CACHE_UNCACHEABLE;
259 case SOUP_STATUS_FOUND:
260 case SOUP_STATUS_TEMPORARY_REDIRECT:
261 /* FIXME: cacheable if explicitly indicated */
262 cacheability = SOUP_CACHE_UNCACHEABLE;
265 case SOUP_STATUS_SEE_OTHER:
266 case SOUP_STATUS_FORBIDDEN:
267 case SOUP_STATUS_NOT_FOUND:
268 case SOUP_STATUS_METHOD_NOT_ALLOWED:
269 return (SOUP_CACHE_UNCACHEABLE | SOUP_CACHE_INVALIDATES);
272 /* Any 5xx status or any 4xx status not handled above
273 * is uncacheable but doesn't break the cache.
275 if ((msg->status_code >= SOUP_STATUS_BAD_REQUEST &&
276 msg->status_code <= SOUP_STATUS_FAILED_DEPENDENCY) ||
277 msg->status_code >= SOUP_STATUS_INTERNAL_SERVER_ERROR)
278 return SOUP_CACHE_UNCACHEABLE;
280 /* An unrecognized 2xx, 3xx, or 4xx response breaks
283 if ((msg->status_code > SOUP_STATUS_PARTIAL_CONTENT &&
284 msg->status_code < SOUP_STATUS_MULTIPLE_CHOICES) ||
285 (msg->status_code > SOUP_STATUS_TEMPORARY_REDIRECT &&
286 msg->status_code < SOUP_STATUS_INTERNAL_SERVER_ERROR))
287 return (SOUP_CACHE_UNCACHEABLE | SOUP_CACHE_INVALIDATES);
294 /* NOTE: this function deletes the file pointed by the file argument
295 * and also unref's the GFile object representing it.
298 soup_cache_entry_free (SoupCacheEntry *entry)
301 #if ENABLE(TIZEN_USER_AGENT_CHECK_IN_CACHE)
302 g_free (entry->user_agent);
303 entry->user_agent = NULL;
305 g_clear_pointer (&entry->headers, soup_message_headers_free);
306 g_clear_object (&entry->cancellable);
308 g_slice_free (SoupCacheEntry, entry);
312 copy_headers (const char *name, const char *value, SoupMessageHeaders *headers)
314 soup_message_headers_append (headers, name, value);
318 remove_headers (const char *name, const char *value, SoupMessageHeaders *headers)
320 soup_message_headers_remove (headers, name);
323 static char *hop_by_hop_headers[] = {"Connection", "Keep-Alive", "Proxy-Authenticate", "Proxy-Authorization", "TE", "Trailer", "Transfer-Encoding", "Upgrade"};
326 copy_end_to_end_headers (SoupMessageHeaders *source, SoupMessageHeaders *destination)
330 soup_message_headers_foreach (source, (SoupMessageHeadersForeachFunc) copy_headers, destination);
331 for (i = 0; i < G_N_ELEMENTS (hop_by_hop_headers); i++)
332 soup_message_headers_remove (destination, hop_by_hop_headers[i]);
333 soup_message_headers_clean_connection_headers (destination);
337 soup_cache_entry_get_current_age (SoupCacheEntry *entry)
339 time_t now = time (NULL);
340 time_t resident_time;
342 resident_time = now - entry->response_time;
343 return entry->corrected_initial_age + resident_time;
346 #if ENABLE_TIZEN_UPDATE_CORRECTED_INITIAL_AGE_FOR_CACHE
348 soup_cache_entry_update_corrected_initial_age (SoupCacheEntry *entry)
351 const char *age, *date;
352 time_t date_value, apparent_age, corrected_received_age, age_value = 0;
354 date = soup_message_headers_get_one (entry->headers, "Date");
357 soup_date = soup_date_new_from_string (date);
358 date_value = soup_date_to_time_t (soup_date);
359 soup_date_free (soup_date);
361 age = soup_message_headers_get_one (entry->headers, "Age");
363 age_value = g_ascii_strtoll (age, NULL, 10);
365 apparent_age = entry->response_time - date_value;
366 corrected_received_age = MAX (apparent_age, age_value);
367 entry->corrected_initial_age = corrected_received_age;
369 entry->corrected_initial_age = time (NULL);
371 TIZEN_LOGI("Update corrected_initial_age(%d)", entry->corrected_initial_age);
377 soup_cache_entry_is_fresh_enough (SoupCacheEntry *entry, gint min_fresh)
379 guint limit = (min_fresh == -1) ? soup_cache_entry_get_current_age (entry) : (guint) min_fresh;
380 return entry->freshness_lifetime > limit;
383 static inline guint32
384 get_cache_key_from_uri (const char *uri)
386 return (guint32) g_str_hash (uri);
390 soup_cache_entry_set_freshness (SoupCacheEntry *entry, SoupMessage *msg, SoupCache *cache)
392 const char *cache_control;
393 const char *expires, *date, *last_modified;
395 /* Reset these values. We have to do this to ensure that
396 * revalidations overwrite previous values for the headers.
398 entry->must_revalidate = FALSE;
399 entry->freshness_lifetime = 0;
401 cache_control = soup_message_headers_get_list (entry->headers, "Cache-Control");
402 if (cache_control && *cache_control) {
403 const char *max_age, *s_maxage;
404 gint64 freshness_lifetime = 0;
406 SoupCachePrivate *priv = SOUP_CACHE_GET_PRIVATE (cache);
408 hash = soup_header_parse_param_list (cache_control);
410 /* Should we re-validate the entry when it goes stale */
411 entry->must_revalidate = g_hash_table_lookup_extended (hash, "must-revalidate", NULL, NULL);
414 if (priv->cache_type == SOUP_CACHE_SHARED) {
415 s_maxage = g_hash_table_lookup (hash, "s-maxage");
417 freshness_lifetime = g_ascii_strtoll (s_maxage, NULL, 10);
418 if (freshness_lifetime) {
419 /* Implies proxy-revalidate. TODO: is it true? */
420 entry->must_revalidate = TRUE;
421 soup_header_free_param_list (hash);
427 /* If 'max-age' cache directive is present, use that */
428 max_age = g_hash_table_lookup (hash, "max-age");
430 freshness_lifetime = g_ascii_strtoll (max_age, NULL, 10);
432 if (freshness_lifetime) {
433 entry->freshness_lifetime = (guint32) MIN (freshness_lifetime, G_MAXUINT32);
434 soup_header_free_param_list (hash);
438 soup_header_free_param_list (hash);
441 /* If the 'Expires' response header is present, use its value
442 * minus the value of the 'Date' response header
444 expires = soup_message_headers_get_one (entry->headers, "Expires");
445 date = soup_message_headers_get_one (entry->headers, "Date");
446 if (expires && date) {
447 SoupDate *expires_d, *date_d;
448 time_t expires_t, date_t;
450 expires_d = soup_date_new_from_string (expires);
452 date_d = soup_date_new_from_string (date);
454 expires_t = soup_date_to_time_t (expires_d);
455 date_t = soup_date_to_time_t (date_d);
457 soup_date_free (expires_d);
458 soup_date_free (date_d);
460 if (expires_t && date_t) {
461 entry->freshness_lifetime = (guint32) MAX (expires_t - date_t, 0);
465 /* If Expires is not a valid date we should
466 treat it as already expired, see section
468 entry->freshness_lifetime = 0;
473 /* Otherwise an heuristic may be used */
475 /* Heuristics MUST NOT be used with stored responses with
476 these status codes (section 2.3.1.1) */
477 if (entry->status_code != SOUP_STATUS_OK &&
478 entry->status_code != SOUP_STATUS_NON_AUTHORITATIVE &&
479 entry->status_code != SOUP_STATUS_PARTIAL_CONTENT &&
480 entry->status_code != SOUP_STATUS_MULTIPLE_CHOICES &&
481 entry->status_code != SOUP_STATUS_MOVED_PERMANENTLY &&
482 entry->status_code != SOUP_STATUS_GONE)
485 /* TODO: attach warning 113 if response's current_age is more
486 than 24h (section 2.3.1.1) when using heuristics */
488 /* Last-Modified based heuristic */
489 last_modified = soup_message_headers_get_one (entry->headers, "Last-Modified");
492 time_t now, last_modified_t;
494 soup_date = soup_date_new_from_string (last_modified);
495 last_modified_t = soup_date_to_time_t (soup_date);
498 #define HEURISTIC_FACTOR 0.1 /* From Section 2.3.1.1 */
500 entry->freshness_lifetime = MAX (0, (now - last_modified_t) * HEURISTIC_FACTOR);
501 soup_date_free (soup_date);
507 /* If all else fails, make the entry expire immediately */
508 entry->freshness_lifetime = 0;
511 static SoupCacheEntry *
512 soup_cache_entry_new (SoupCache *cache, SoupMessage *msg, time_t request_time, time_t response_time)
514 SoupCacheEntry *entry;
516 #if ENABLE(TIZEN_USER_AGENT_CHECK_IN_CACHE)
521 entry = g_slice_new0 (SoupCacheEntry);
522 entry->dirty = FALSE;
523 entry->being_validated = FALSE;
524 entry->status_code = msg->status_code;
525 entry->response_time = response_time;
526 entry->uri = soup_uri_to_string (soup_message_get_uri (msg), FALSE);
529 entry->headers = soup_message_headers_new (SOUP_MESSAGE_HEADERS_RESPONSE);
530 copy_end_to_end_headers (msg->response_headers, entry->headers);
532 #if ENABLE(TIZEN_USER_AGENT_CHECK_IN_CACHE)
534 ua = soup_message_headers_get_one(msg->request_headers, "User-Agent");
536 str = g_string_new(ua);
537 entry->user_agent = str->str;
544 /* Section 2.3.1, Freshness Lifetime */
545 soup_cache_entry_set_freshness (entry, msg, cache);
547 /* Section 2.3.2, Calculating Age */
548 date = soup_message_headers_get_one (entry->headers, "Date");
553 time_t date_value, apparent_age, corrected_received_age, response_delay, age_value = 0;
555 soup_date = soup_date_new_from_string (date);
556 date_value = soup_date_to_time_t (soup_date);
557 soup_date_free (soup_date);
559 age = soup_message_headers_get_one (entry->headers, "Age");
561 age_value = g_ascii_strtoll (age, NULL, 10);
563 apparent_age = MAX (0, entry->response_time - date_value);
564 corrected_received_age = MAX (apparent_age, age_value);
565 response_delay = entry->response_time - request_time;
566 entry->corrected_initial_age = corrected_received_age + response_delay;
568 /* Is this correct ? */
569 entry->corrected_initial_age = time (NULL);
576 soup_cache_entry_remove (SoupCache *cache, SoupCacheEntry *entry, gboolean purge)
581 g_cancellable_cancel (entry->cancellable);
585 g_assert (!entry->dirty);
586 g_assert (g_list_length (cache->priv->lru_start) == g_hash_table_size (cache->priv->cache));
588 if (!g_hash_table_remove (cache->priv->cache, GUINT_TO_POINTER (entry->key)))
591 /* Remove from LRU */
592 lru_item = g_list_find (cache->priv->lru_start, entry);
593 cache->priv->lru_start = g_list_delete_link (cache->priv->lru_start, lru_item);
595 /* Adjust cache size */
596 cache->priv->size -= entry->length;
598 g_assert (g_list_length (cache->priv->lru_start) == g_hash_table_size (cache->priv->cache));
602 GFile *file = get_file_from_entry (cache, entry);
603 g_file_delete (file, NULL, NULL);
604 g_object_unref (file);
606 soup_cache_entry_free (entry);
612 lru_compare_func (gconstpointer a, gconstpointer b)
614 SoupCacheEntry *entry_a = (SoupCacheEntry *)a;
615 SoupCacheEntry *entry_b = (SoupCacheEntry *)b;
617 /* The rationale of this sorting func is
619 * 1. sort by hits -> LRU algorithm, then
621 * 2. sort by freshness lifetime, we better discard first
622 * entries that are close to expire
624 * 3. sort by size, replace first small size resources as they
625 * are cheaper to download
629 if (entry_a->hits != entry_b->hits)
630 return entry_a->hits - entry_b->hits;
632 /* Sort by freshness_lifetime */
633 if (entry_a->freshness_lifetime != entry_b->freshness_lifetime)
634 return entry_a->freshness_lifetime - entry_b->freshness_lifetime;
637 return entry_a->length - entry_b->length;
641 cache_accepts_entries_of_size (SoupCache *cache, guint length_to_add)
643 /* We could add here some more heuristics. TODO: review how
644 this is done by other HTTP caches */
646 return length_to_add <= cache->priv->max_entry_data_size;
650 make_room_for_new_entry (SoupCache *cache, guint length_to_add)
652 GList *lru_entry = cache->priv->lru_start;
654 /* Check that there is enough room for the new entry. This is
655 an approximation as we're not working out the size of the
656 cache file or the size of the headers for performance
657 reasons. TODO: check if that would be really that expensive */
660 (length_to_add + cache->priv->size > cache->priv->max_size)) {
661 SoupCacheEntry *old_entry = (SoupCacheEntry *)lru_entry->data;
663 /* Discard entries. Once cancelled resources will be
664 * freed in close_ready_cb
666 if (soup_cache_entry_remove (cache, old_entry, TRUE))
667 lru_entry = cache->priv->lru_start;
669 lru_entry = g_list_next (lru_entry);
674 soup_cache_entry_insert (SoupCache *cache,
675 SoupCacheEntry *entry,
678 guint length_to_add = 0;
679 SoupCacheEntry *old_entry;
682 #if ENABLE(TIZEN_TV_CHECKING_DELETED_ENTRY_FILE)
685 entry->key = get_cache_key_from_uri ((const char *) entry->uri);
687 if (soup_message_headers_get_encoding (entry->headers) == SOUP_ENCODING_CONTENT_LENGTH)
688 #if ENABLE(TIZEN_TV_COMPUTING_DISK_CACHE_SIZE)
691 length_to_add = entry->length;
694 length_to_add = soup_message_headers_get_content_length (entry->headers);
698 length_to_add = soup_message_headers_get_content_length (entry->headers);
701 /* Check if we are going to store the resource depending on its size */
703 if (!cache_accepts_entries_of_size (cache, length_to_add))
706 /* Make room for new entry if needed */
707 make_room_for_new_entry (cache, length_to_add);
710 /* Remove any previous entry */
711 if ((old_entry = g_hash_table_lookup (cache->priv->cache, GUINT_TO_POINTER (entry->key))) != NULL) {
712 if (!soup_cache_entry_remove (cache, old_entry, TRUE))
716 /* Add to hash table */
717 g_hash_table_insert (cache->priv->cache, GUINT_TO_POINTER (entry->key), entry);
719 /* Compute new cache size */
720 cache->priv->size += length_to_add;
724 cache->priv->lru_start = g_list_insert_sorted (cache->priv->lru_start, entry, lru_compare_func);
726 cache->priv->lru_start = g_list_prepend (cache->priv->lru_start, entry);
728 g_assert (g_list_length (cache->priv->lru_start) == g_hash_table_size (cache->priv->cache));
733 static SoupCacheEntry*
734 soup_cache_entry_lookup (SoupCache *cache,
737 SoupCacheEntry *entry;
741 uri = soup_uri_to_string (soup_message_get_uri (msg), FALSE);
742 key = get_cache_key_from_uri ((const char *) uri);
744 entry = g_hash_table_lookup (cache->priv->cache, GUINT_TO_POINTER (key));
746 if (entry != NULL && (strcmp (entry->uri, uri) != 0))
754 soup_cache_send_response (SoupCache *cache, SoupMessage *msg)
756 SoupCacheEntry *entry;
758 GInputStream *file_stream, *body_stream, *cache_stream;
760 #if ENABLE(TIZEN_TV_ADD_X_SOUP_MESSAGE_HEADERS)
764 g_return_val_if_fail (SOUP_IS_CACHE (cache), NULL);
765 g_return_val_if_fail (SOUP_IS_MESSAGE (msg), NULL);
767 entry = soup_cache_entry_lookup (cache, msg);
768 g_return_val_if_fail (entry, NULL);
770 file = get_file_from_entry (cache, entry);
771 file_stream = G_INPUT_STREAM (g_file_read (file, NULL, NULL));
772 g_object_unref (file);
774 /* Do not change the original message if there is no resource */
778 body_stream = soup_body_input_stream_new (file_stream, SOUP_ENCODING_CONTENT_LENGTH, entry->length);
779 g_object_unref (file_stream);
784 /* If we are told to send a response from cache any validation
785 in course is over by now */
786 entry->being_validated = FALSE;
789 soup_message_set_status (msg, entry->status_code);
792 copy_end_to_end_headers (entry->headers, msg->response_headers);
794 /* Add 'Age' header with the current age */
795 current_age = g_strdup_printf ("%d", soup_cache_entry_get_current_age (entry));
796 soup_message_headers_replace (msg->response_headers,
799 g_free (current_age);
801 #if ENABLE(TIZEN_TV_ADD_X_SOUP_MESSAGE_HEADERS)
802 /* Add 'X-From-Cache' header */
803 soup_message_headers_append(msg->response_headers, "X-From-Cache", "true");
805 /* Add 'X-Entry-Length' header */
806 entry_length = g_strdup_printf("%u", entry->length);
807 soup_message_headers_append(msg->response_headers, "X-Entry-Length", entry_length);
808 g_free (entry_length);
811 /* Create the cache stream. */
812 soup_message_disable_feature (msg, SOUP_TYPE_CACHE);
813 cache_stream = soup_message_setup_body_istream (body_stream, msg,
814 cache->priv->session,
815 SOUP_STAGE_ENTITY_BODY);
816 g_object_unref (body_stream);
822 msg_got_headers_cb (SoupMessage *msg, gpointer user_data)
824 g_object_set_data (G_OBJECT (msg), "response-time", GINT_TO_POINTER (time (NULL)));
825 g_signal_handlers_disconnect_by_func (msg, msg_got_headers_cb, user_data);
829 request_started (SoupSessionFeature *feature, SoupSession *session,
830 SoupMessage *msg, SoupSocket *socket)
832 g_object_set_data (G_OBJECT (msg), "request-time", GINT_TO_POINTER (time (NULL)));
833 g_signal_connect (msg, "got-headers", G_CALLBACK (msg_got_headers_cb), NULL);
837 attach (SoupSessionFeature *feature, SoupSession *session)
839 SoupCache *cache = SOUP_CACHE (feature);
840 cache->priv->session = session;
842 soup_cache_default_feature_interface->attach (feature, session);
846 soup_cache_session_feature_init (SoupSessionFeatureInterface *feature_interface,
847 gpointer interface_data)
849 soup_cache_default_feature_interface =
850 g_type_default_interface_peek (SOUP_TYPE_SESSION_FEATURE);
852 feature_interface->attach = attach;
853 feature_interface->request_started = request_started;
858 SoupCacheEntry *entry;
862 istream_caching_finished (SoupCacheInputStream *istream,
867 StreamHelper *helper = (StreamHelper *) user_data;
868 SoupCache *cache = helper->cache;
869 SoupCacheEntry *entry = helper->entry;
871 --cache->priv->n_pending;
873 entry->dirty = FALSE;
874 entry->length = bytes_written;
875 g_clear_object (&entry->cancellable);
878 /* Update cache size */
879 if (soup_message_headers_get_encoding (entry->headers) == SOUP_ENCODING_CONTENT_LENGTH)
880 cache->priv->size -= soup_message_headers_get_content_length (entry->headers);
882 soup_cache_entry_remove (cache, entry, TRUE);
883 helper->entry = entry = NULL;
887 if (soup_message_headers_get_encoding (entry->headers) != SOUP_ENCODING_CONTENT_LENGTH) {
889 if (cache_accepts_entries_of_size (cache, entry->length)) {
890 make_room_for_new_entry (cache, entry->length);
891 cache->priv->size += entry->length;
893 soup_cache_entry_remove (cache, entry, TRUE);
894 helper->entry = entry = NULL;
899 g_object_unref (helper->cache);
900 g_slice_free (StreamHelper, helper);
904 soup_cache_content_processor_wrap_input (SoupContentProcessor *processor,
905 GInputStream *base_stream,
909 SoupCache *cache = (SoupCache*) processor;
910 SoupCacheEntry *entry;
911 SoupCacheability cacheability;
912 GInputStream *istream;
914 StreamHelper *helper;
915 time_t request_time, response_time;
917 /* First of all, check if we should cache the resource. */
918 cacheability = soup_cache_get_cacheability (cache, msg);
919 entry = soup_cache_entry_lookup (cache, msg);
921 if (cacheability & SOUP_CACHE_INVALIDATES) {
923 soup_cache_entry_remove (cache, entry, TRUE);
927 if (cacheability & SOUP_CACHE_VALIDATES) {
928 /* It's possible to get a CACHE_VALIDATES with no
929 * entry in the hash table. This could happen if for
930 * example the soup client is the one creating the
931 * conditional request.
934 soup_cache_update_from_conditional_request (cache, msg);
938 if (!(cacheability & SOUP_CACHE_CACHEABLE))
941 /* Check if we are already caching this resource */
942 if (entry && (entry->dirty || entry->being_validated))
945 /* Create a new entry, deleting any old one if present */
947 soup_cache_entry_remove (cache, entry, TRUE);
949 request_time = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (msg), "request-time"));
950 response_time = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (msg), "request-time"));
951 entry = soup_cache_entry_new (cache, msg, request_time, response_time);
955 /* Do not continue if it can not be stored */
956 if (!soup_cache_entry_insert (cache, entry, TRUE)) {
957 soup_cache_entry_free (entry);
961 entry->cancellable = g_cancellable_new ();
962 ++cache->priv->n_pending;
964 helper = g_slice_new (StreamHelper);
965 helper->cache = g_object_ref (cache);
966 helper->entry = entry;
968 file = get_file_from_entry (cache, entry);
969 istream = soup_cache_input_stream_new (base_stream, file);
970 g_object_unref (file);
972 g_signal_connect (istream, "caching-finished", G_CALLBACK (istream_caching_finished), helper);
978 soup_cache_content_processor_init (SoupContentProcessorInterface *processor_interface,
979 gpointer interface_data)
981 soup_cache_default_content_processor_interface =
982 g_type_default_interface_peek (SOUP_TYPE_CONTENT_PROCESSOR);
984 processor_interface->processing_stage = SOUP_STAGE_ENTITY_BODY;
985 processor_interface->wrap_input = soup_cache_content_processor_wrap_input;
989 soup_cache_init (SoupCache *cache)
991 SoupCachePrivate *priv;
993 priv = cache->priv = SOUP_CACHE_GET_PRIVATE (cache);
995 priv->cache = g_hash_table_new (g_direct_hash, g_direct_equal);
997 priv->lru_start = NULL;
1000 priv->n_pending = 0;
1003 priv->max_size = DEFAULT_MAX_SIZE;
1004 priv->max_entry_data_size = priv->max_size / MAX_ENTRY_DATA_PERCENTAGE;
1009 remove_cache_item (gpointer data,
1012 soup_cache_entry_remove ((SoupCache *) user_data, (SoupCacheEntry *) data, FALSE);
1016 soup_cache_finalize (GObject *object)
1018 SoupCachePrivate *priv;
1021 priv = SOUP_CACHE (object)->priv;
1023 // Cannot use g_hash_table_foreach as callbacks must not modify the hash table
1024 entries = g_hash_table_get_values (priv->cache);
1025 g_list_foreach (entries, remove_cache_item, object);
1026 g_list_free (entries);
1028 g_hash_table_destroy (priv->cache);
1029 g_free (priv->cache_dir);
1031 g_list_free (priv->lru_start);
1033 G_OBJECT_CLASS (soup_cache_parent_class)->finalize (object);
1037 soup_cache_set_property (GObject *object, guint prop_id,
1038 const GValue *value, GParamSpec *pspec)
1040 SoupCachePrivate *priv = SOUP_CACHE (object)->priv;
1043 case PROP_CACHE_DIR:
1044 priv->cache_dir = g_value_dup_string (value);
1045 /* Create directory if it does not exist (FIXME: should we?) */
1046 if (!g_file_test (priv->cache_dir, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR))
1047 g_mkdir_with_parents (priv->cache_dir, 0700);
1049 case PROP_CACHE_TYPE:
1050 priv->cache_type = g_value_get_enum (value);
1051 /* TODO: clear private entries and issue a warning if moving to shared? */
1054 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1060 soup_cache_get_property (GObject *object, guint prop_id,
1061 GValue *value, GParamSpec *pspec)
1063 SoupCachePrivate *priv = SOUP_CACHE (object)->priv;
1066 case PROP_CACHE_DIR:
1067 g_value_set_string (value, priv->cache_dir);
1069 case PROP_CACHE_TYPE:
1070 g_value_set_enum (value, priv->cache_type);
1073 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1079 soup_cache_constructed (GObject *object)
1081 SoupCachePrivate *priv;
1083 priv = SOUP_CACHE (object)->priv;
1085 if (!priv->cache_dir) {
1086 /* Set a default cache dir, different for each user */
1087 priv->cache_dir = g_build_filename (g_get_user_cache_dir (),
1090 if (!g_file_test (priv->cache_dir, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR))
1091 g_mkdir_with_parents (priv->cache_dir, 0700);
1094 if (G_OBJECT_CLASS (soup_cache_parent_class)->constructed)
1095 G_OBJECT_CLASS (soup_cache_parent_class)->constructed (object);
1099 soup_cache_class_init (SoupCacheClass *cache_class)
1101 GObjectClass *gobject_class = (GObjectClass *)cache_class;
1103 gobject_class->finalize = soup_cache_finalize;
1104 gobject_class->constructed = soup_cache_constructed;
1105 gobject_class->set_property = soup_cache_set_property;
1106 gobject_class->get_property = soup_cache_get_property;
1108 cache_class->get_cacheability = get_cacheability;
1110 g_object_class_install_property (gobject_class, PROP_CACHE_DIR,
1111 g_param_spec_string ("cache-dir",
1113 "The directory to store the cache files",
1115 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
1117 g_object_class_install_property (gobject_class, PROP_CACHE_TYPE,
1118 g_param_spec_enum ("cache-type",
1120 "Whether the cache is private or shared",
1121 SOUP_TYPE_CACHE_TYPE,
1122 SOUP_CACHE_SINGLE_USER,
1123 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
1125 g_type_class_add_private (cache_class, sizeof (SoupCachePrivate));
1130 * @SOUP_CACHE_SINGLE_USER: a single-user cache
1131 * @SOUP_CACHE_SHARED: a shared cache
1133 * The type of cache; this affects what kinds of responses will be
1141 * @cache_dir: the directory to store the cached data, or %NULL to use the default one
1142 * @cache_type: the #SoupCacheType of the cache
1144 * Creates a new #SoupCache.
1146 * Returns: a new #SoupCache
1151 soup_cache_new (const char *cache_dir, SoupCacheType cache_type)
1153 return g_object_new (SOUP_TYPE_CACHE,
1154 "cache-dir", cache_dir,
1155 "cache-type", cache_type,
1160 * soup_cache_has_response:
1161 * @cache: a #SoupCache
1162 * @msg: a #SoupMessage
1164 * This function calculates whether the @cache object has a proper
1165 * response for the request @msg given the flags both in the request
1166 * and the cached reply and the time ellapsed since it was cached.
1168 * Returns: whether or not the @cache has a valid response for @msg
1173 soup_cache_has_response (SoupCache *cache, SoupMessage *msg)
1175 SoupCacheEntry *entry;
1176 const char *cache_control, *pragma;
1178 int max_age, max_stale, min_fresh;
1179 GList *lru_item, *item;
1180 #if ENABLE(TIZEN_USER_AGENT_CHECK_IN_CACHE)
1183 #if ENABLE(TIZEN_CACHE_FILE_SIZE_VALIDATION)
1185 GFileInfo *file_info;
1188 entry = soup_cache_entry_lookup (cache, msg);
1190 /* 1. The presented Request-URI and that of stored response
1194 return SOUP_CACHE_RESPONSE_STALE;
1196 #if ENABLE(TIZEN_CACHE_FILE_SIZE_VALIDATION)
1197 file = get_file_from_entry (cache, entry);
1198 file_info = g_file_query_info (file, G_FILE_ATTRIBUTE_STANDARD_SIZE,
1199 G_FILE_QUERY_INFO_NONE, NULL, NULL);
1201 if (file_info && (file_size = g_file_info_get_size (file_info)) != entry->length) {
1202 soup_cache_entry_remove(cache, entry, TRUE);
1203 g_file_delete (file, NULL, NULL);
1204 g_object_unref (file_info);
1205 g_object_unref (file);
1206 return SOUP_CACHE_RESPONSE_STALE;
1208 g_object_unref (file_info);
1209 g_object_unref (file);
1212 /* Increase hit count. Take sorting into account */
1214 lru_item = g_list_find (cache->priv->lru_start, entry);
1216 while (item->next && lru_compare_func (item->data, item->next->data) > 0)
1217 item = g_list_next (item);
1219 if (item != lru_item) {
1220 cache->priv->lru_start = g_list_remove_link (cache->priv->lru_start, lru_item);
1221 item = g_list_insert_sorted (item, lru_item->data, lru_compare_func);
1222 g_list_free (lru_item);
1225 if (entry->dirty || entry->being_validated)
1226 return SOUP_CACHE_RESPONSE_STALE;
1228 /* 2. The request method associated with the stored response
1229 * allows it to be used for the presented request
1232 /* In practice this means we only return our resource for GET,
1233 * cacheability for other methods is a TODO in the RFC
1234 * (TODO: although we could return the headers for HEAD
1237 if (msg->method != SOUP_METHOD_GET)
1238 return SOUP_CACHE_RESPONSE_STALE;
1240 /* 3. Selecting request-headers nominated by the stored
1241 * response (if any) match those presented.
1246 /* 4. The request is a conditional request issued by the client.
1248 if (soup_message_headers_get_one (msg->request_headers, "If-Modified-Since") ||
1249 soup_message_headers_get_list (msg->request_headers, "If-None-Match"))
1250 return SOUP_CACHE_RESPONSE_STALE;
1252 /* 5. The presented request and stored response are free from
1253 * directives that would prevent its use.
1256 max_age = max_stale = min_fresh = -1;
1258 /* For HTTP 1.0 compatibility. RFC2616 section 14.9.4
1260 pragma = soup_message_headers_get_list (msg->request_headers, "Pragma");
1261 if (pragma && soup_header_contains (pragma, "no-cache"))
1262 return SOUP_CACHE_RESPONSE_STALE;
1264 #if ENABLE(TIZEN_USER_AGENT_CHECK_IN_CACHE)
1265 ua = soup_message_headers_get_one (msg->request_headers, "User-Agent");
1266 if (ua && entry->user_agent) {
1267 if (strcmp (ua, entry->user_agent))
1268 return SOUP_CACHE_RESPONSE_STALE;
1272 cache_control = soup_message_headers_get_list (msg->request_headers, "Cache-Control");
1273 if (cache_control && *cache_control) {
1274 GHashTable *hash = soup_header_parse_param_list (cache_control);
1276 if (g_hash_table_lookup_extended (hash, "no-store", NULL, NULL)) {
1277 soup_header_free_param_list (hash);
1278 return SOUP_CACHE_RESPONSE_STALE;
1281 if (g_hash_table_lookup_extended (hash, "no-cache", NULL, NULL)) {
1282 soup_header_free_param_list (hash);
1283 return SOUP_CACHE_RESPONSE_STALE;
1286 #if ENABLE (TIZEN_HANDLE_MALFORMED_MAX_AGE_HEADER)
1287 if (g_hash_table_lookup_extended (hash, "max-age", NULL, &value) && value) {
1289 if (g_hash_table_lookup_extended (hash, "max-age", NULL, &value)) {
1291 max_age = (int)MIN (g_ascii_strtoll (value, NULL, 10), G_MAXINT32);
1292 /* Forcing cache revalidaton
1295 soup_header_free_param_list (hash);
1296 return SOUP_CACHE_RESPONSE_NEEDS_VALIDATION;
1300 /* max-stale can have no value set, we need to use _extended */
1301 if (g_hash_table_lookup_extended (hash, "max-stale", NULL, &value)) {
1303 max_stale = (int)MIN (g_ascii_strtoll (value, NULL, 10), G_MAXINT32);
1305 max_stale = G_MAXINT32;
1308 value = g_hash_table_lookup (hash, "min-fresh");
1310 min_fresh = (int)MIN (g_ascii_strtoll (value, NULL, 10), G_MAXINT32);
1312 soup_header_free_param_list (hash);
1315 guint current_age = soup_cache_entry_get_current_age (entry);
1317 /* If we are over max-age and max-stale is not
1318 set, do not use the value from the cache
1319 without validation */
1320 if ((guint) max_age <= current_age && max_stale == -1)
1321 return SOUP_CACHE_RESPONSE_NEEDS_VALIDATION;
1325 /* 6. The stored response is either: fresh, allowed to be
1326 * served stale or succesfully validated
1328 /* TODO consider also proxy-revalidate & s-maxage */
1329 if (entry->must_revalidate)
1330 return SOUP_CACHE_RESPONSE_NEEDS_VALIDATION;
1332 if (!soup_cache_entry_is_fresh_enough (entry, min_fresh)) {
1333 /* Not fresh, can it be served stale? */
1334 if (max_stale != -1) {
1335 /* G_MAXINT32 means we accept any staleness */
1336 if (max_stale == G_MAXINT32)
1337 return SOUP_CACHE_RESPONSE_FRESH;
1339 if ((soup_cache_entry_get_current_age (entry) - entry->freshness_lifetime) <= (guint) max_stale)
1340 return SOUP_CACHE_RESPONSE_FRESH;
1343 return SOUP_CACHE_RESPONSE_NEEDS_VALIDATION;
1346 return SOUP_CACHE_RESPONSE_FRESH;
1350 * soup_cache_get_cacheability:
1351 * @cache: a #SoupCache
1352 * @msg: a #SoupMessage
1354 * Calculates whether the @msg can be cached or not.
1356 * Returns: a #SoupCacheability value indicating whether the @msg can be cached or not.
1361 soup_cache_get_cacheability (SoupCache *cache, SoupMessage *msg)
1363 g_return_val_if_fail (SOUP_IS_CACHE (cache), SOUP_CACHE_UNCACHEABLE);
1364 g_return_val_if_fail (SOUP_IS_MESSAGE (msg), SOUP_CACHE_UNCACHEABLE);
1366 return SOUP_CACHE_GET_CLASS (cache)->get_cacheability (cache, msg);
1370 force_flush_timeout (gpointer data)
1372 gboolean *forced = (gboolean *)data;
1380 * @cache: a #SoupCache
1382 * This function will force all pending writes in the @cache to be
1383 * committed to disk. For doing so it will iterate the #GMainContext
1384 * associated with @cache's session as long as needed.
1386 * Contrast with soup_cache_dump(), which writes out the cache index
1392 soup_cache_flush (SoupCache *cache)
1394 GMainContext *async_context;
1395 SoupSession *session;
1397 gboolean forced = FALSE;
1399 g_return_if_fail (SOUP_IS_CACHE (cache));
1401 session = cache->priv->session;
1402 g_return_if_fail (SOUP_IS_SESSION (session));
1403 async_context = soup_session_get_async_context (session);
1405 /* We give cache 10 secs to finish */
1406 timeout = soup_add_timeout (async_context, 10000, force_flush_timeout, &forced);
1408 while (!forced && cache->priv->n_pending > 0)
1409 g_main_context_iteration (async_context, FALSE);
1412 g_source_destroy (timeout);
1414 g_warning ("Cache flush finished despite %d pending requests", cache->priv->n_pending);
1417 #if ENABLE(TIZEN_TV_SOUP_CACHE_CLEAN_LEAKED_RESOURCES)
1418 typedef void (* SoupCacheForeachFileFunc) (SoupCache *cache, const char *name, gpointer user_data);
1421 soup_cache_foreach_file (SoupCache *cache, SoupCacheForeachFileFunc func, gpointer user_data)
1425 SoupCachePrivate *priv = cache->priv;
1427 dir = g_dir_open (priv->cache_dir, 0, NULL);
1428 while ((name = g_dir_read_name (dir))) {
1429 if (g_str_has_prefix (name, "soup."))
1432 func (cache, name, user_data);
1437 #if ENABLE(TIZEN_TV_SOUP_CACHE_OPTIMISE_LOAD_TIME)
1440 * When a list of regular files is required, examining the d_type field returned from readdir() is faster
1441 * than using g_file_test(G_FILE_TEST_IS_REGULAR).
1445 soup_cache_foreach_regular_file (SoupCache *cache, SoupCacheForeachFileFunc func, gpointer user_data)
1449 SoupCachePrivate *priv = cache->priv;
1451 dir = opendir(priv->cache_dir);
1454 struct dirent dent_buf;
1455 while (!readdir_r(dir, &dent_buf, &dp) && dp) {
1456 if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, ".."))
1460 if (g_str_has_prefix (name, "soup."))
1462 else if (dp->d_type == DT_UNKNOWN) {
1463 // This path should not be executed normally, but is included as a fail safe.
1464 gchar *path = g_build_filename(priv->cache_dir, name, NULL);
1465 const int isreg = g_file_test (path, G_FILE_TEST_IS_REGULAR);
1469 } else if (dp->d_type != DT_REG)
1472 func (cache, name, user_data);
1481 delete_cache_file (SoupCache *cache, const char *name, gpointer user_data)
1485 path = g_build_filename (cache->priv->cache_dir, name, NULL);
1492 clear_cache_item (gpointer data,
1495 soup_cache_entry_remove ((SoupCache *) user_data, (SoupCacheEntry *) data, TRUE);
1499 clear_cache_files (SoupCache *cache)
1501 #if ENABLE(TIZEN_TV_SOUP_CACHE_CLEAN_LEAKED_RESOURCES)
1502 soup_cache_foreach_file (cache, delete_cache_file, NULL);
1504 GFileInfo *file_info;
1505 GFileEnumerator *file_enumerator;
1506 GFile *cache_dir_file = g_file_new_for_path (cache->priv->cache_dir);
1508 file_enumerator = g_file_enumerate_children (cache_dir_file, G_FILE_ATTRIBUTE_STANDARD_NAME,
1509 G_FILE_QUERY_INFO_NONE, NULL, NULL);
1510 if (file_enumerator) {
1511 while ((file_info = g_file_enumerator_next_file (file_enumerator, NULL, NULL)) != NULL) {
1512 const char *filename = g_file_info_get_name (file_info);
1514 if (strcmp (filename, SOUP_CACHE_FILE) != 0) {
1515 GFile *cache_file = g_file_get_child (cache_dir_file, filename);
1516 g_file_delete (cache_file, NULL, NULL);
1517 g_object_unref (cache_file);
1519 g_object_unref (file_info);
1521 g_object_unref (file_enumerator);
1523 g_object_unref (cache_dir_file);
1529 * @cache: a #SoupCache
1531 * Will remove all entries in the @cache plus all the cache files.
1536 soup_cache_clear (SoupCache *cache)
1540 g_return_if_fail (SOUP_IS_CACHE (cache));
1541 g_return_if_fail (cache->priv->cache);
1543 // Cannot use g_hash_table_foreach as callbacks must not modify the hash table
1544 entries = g_hash_table_get_values (cache->priv->cache);
1545 g_list_foreach (entries, clear_cache_item, cache);
1546 g_list_free (entries);
1548 /* Remove also any file not associated with a cache entry. */
1549 clear_cache_files (cache);
1553 soup_cache_generate_conditional_request (SoupCache *cache, SoupMessage *original)
1557 SoupCacheEntry *entry;
1558 const char *last_modified, *etag;
1559 SoupMessagePrivate *origpriv;
1562 g_return_val_if_fail (SOUP_IS_CACHE (cache), NULL);
1563 g_return_val_if_fail (SOUP_IS_MESSAGE (original), NULL);
1565 /* Add the validator entries in the header from the cached data */
1566 entry = soup_cache_entry_lookup (cache, original);
1567 g_return_val_if_fail (entry, NULL);
1569 last_modified = soup_message_headers_get_one (entry->headers, "Last-Modified");
1570 etag = soup_message_headers_get_one (entry->headers, "ETag");
1572 if (!last_modified && !etag)
1575 entry->being_validated = TRUE;
1577 /* Copy the data we need from the original message */
1578 uri = soup_message_get_uri (original);
1579 msg = soup_message_new_from_uri (original->method, uri);
1580 soup_message_disable_feature (msg, SOUP_TYPE_CACHE);
1582 soup_message_headers_foreach (original->request_headers,
1583 (SoupMessageHeadersForeachFunc)copy_headers,
1584 msg->request_headers);
1586 origpriv = SOUP_MESSAGE_GET_PRIVATE (original);
1587 for (f = origpriv->disabled_features; f; f = f->next)
1588 soup_message_disable_feature (msg, (GType) GPOINTER_TO_SIZE (f->data));
1591 soup_message_headers_append (msg->request_headers,
1592 "If-Modified-Since",
1595 soup_message_headers_append (msg->request_headers,
1603 soup_cache_cancel_conditional_request (SoupCache *cache,
1606 SoupCacheEntry *entry;
1608 entry = soup_cache_entry_lookup (cache, msg);
1610 entry->being_validated = FALSE;
1612 soup_session_cancel_message (cache->priv->session, msg, SOUP_STATUS_CANCELLED);
1616 soup_cache_update_from_conditional_request (SoupCache *cache,
1619 SoupCacheEntry *entry = soup_cache_entry_lookup (cache, msg);
1623 entry->being_validated = FALSE;
1625 if (msg->status_code == SOUP_STATUS_NOT_MODIFIED) {
1626 soup_message_headers_foreach (msg->response_headers,
1627 (SoupMessageHeadersForeachFunc) remove_headers,
1629 copy_end_to_end_headers (msg->response_headers, entry->headers);
1631 soup_cache_entry_set_freshness (entry, msg, cache);
1632 #if ENABLE_TIZEN_UPDATE_CORRECTED_INITIAL_AGE_FOR_CACHE
1633 soup_cache_entry_update_corrected_initial_age (entry);
1634 entry->response_time = time (NULL);
1640 pack_entry (gpointer data,
1643 SoupCacheEntry *entry = (SoupCacheEntry *) data;
1644 SoupMessageHeadersIter iter;
1645 const char *header_key, *header_value;
1646 GVariantBuilder *entries_builder = (GVariantBuilder *)user_data;
1648 /* Do not store non-consolidated entries */
1649 if (entry->dirty || !entry->key)
1652 g_variant_builder_open (entries_builder, G_VARIANT_TYPE (SOUP_CACHE_PHEADERS_FORMAT));
1653 #if ENABLE(TIZEN_FIX_PACK_ENTRY)
1654 if (!g_utf8_validate (entry->uri, -1, NULL)) {
1655 g_variant_builder_close (entries_builder);
1659 g_variant_builder_add (entries_builder, "s", entry->uri);
1660 g_variant_builder_add (entries_builder, "b", entry->must_revalidate);
1661 g_variant_builder_add (entries_builder, "u", entry->freshness_lifetime);
1662 g_variant_builder_add (entries_builder, "u", entry->corrected_initial_age);
1663 g_variant_builder_add (entries_builder, "u", entry->response_time);
1664 g_variant_builder_add (entries_builder, "u", entry->hits);
1665 g_variant_builder_add (entries_builder, "u", entry->length);
1666 g_variant_builder_add (entries_builder, "q", entry->status_code);
1667 #if ENABLE(TIZEN_USER_AGENT_CHECK_IN_CACHE)
1668 g_variant_builder_add (entries_builder, "s", entry->user_agent);
1671 g_variant_builder_open (entries_builder, G_VARIANT_TYPE ("a" SOUP_CACHE_HEADERS_FORMAT));
1672 soup_message_headers_iter_init (&iter, entry->headers);
1673 while (soup_message_headers_iter_next (&iter, &header_key, &header_value)) {
1674 if (g_utf8_validate (header_value, -1, NULL))
1675 g_variant_builder_add (entries_builder, SOUP_CACHE_HEADERS_FORMAT,
1676 header_key, header_value);
1678 g_variant_builder_close (entries_builder); /* "a" SOUP_CACHE_HEADERS_FORMAT */
1679 g_variant_builder_close (entries_builder); /* SOUP_CACHE_PHEADERS_FORMAT */
1684 * @cache: a #SoupCache
1686 * Synchronously writes the cache index out to disk. Contrast with
1687 * soup_cache_flush(), which writes pending cache
1688 * <emphasis>entries</emphasis> to disk.
1690 * You must call this before exiting if you want your cache data to
1691 * persist between sessions.
1696 soup_cache_dump (SoupCache *cache)
1698 SoupCachePrivate *priv = SOUP_CACHE_GET_PRIVATE (cache);
1700 GVariantBuilder entries_builder;
1701 GVariant *cache_variant;
1703 if (!g_list_length (cache->priv->lru_start))
1704 #if ENABLE(TIZEN_FIX_CACHE_DUMP)
1707 filename = g_build_filename (priv->cache_dir, SOUP_CACHE_FILE, NULL);
1708 file = g_file_new_for_path (filename);
1710 g_file_delete (file, NULL, NULL);
1711 g_object_unref (file);
1719 /* Create the builder and iterate over all entries */
1720 g_variant_builder_init (&entries_builder, G_VARIANT_TYPE (SOUP_CACHE_ENTRIES_FORMAT));
1721 g_variant_builder_add (&entries_builder, "q", SOUP_CACHE_CURRENT_VERSION);
1722 g_variant_builder_open (&entries_builder, G_VARIANT_TYPE ("a" SOUP_CACHE_PHEADERS_FORMAT));
1723 g_list_foreach (cache->priv->lru_start, pack_entry, &entries_builder);
1724 g_variant_builder_close (&entries_builder);
1726 /* Serialize and dump */
1727 cache_variant = g_variant_builder_end (&entries_builder);
1728 g_variant_ref_sink (cache_variant);
1729 filename = g_build_filename (priv->cache_dir, SOUP_CACHE_FILE, NULL);
1730 g_file_set_contents (filename, (const char *) g_variant_get_data (cache_variant),
1731 g_variant_get_size (cache_variant), NULL);
1733 g_variant_unref (cache_variant);
1736 #if ENABLE(TIZEN_TV_SOUP_CACHE_CLEAN_LEAKED_RESOURCES)
1737 static inline guint32
1738 get_key_from_cache_filename (const char *name)
1742 key = g_ascii_strtoull (name, NULL, 10);
1743 return key ? (guint32)key : 0;
1747 insert_cache_file (SoupCache *cache, const char *name, GHashTable *leaked_entries)
1751 path = g_build_filename (cache->priv->cache_dir, name, NULL);
1752 #if !ENABLE(TIZEN_TV_SOUP_CACHE_OPTIMISE_LOAD_TIME)
1753 if (g_file_test (path, G_FILE_TEST_IS_REGULAR))
1756 guint32 key = get_key_from_cache_filename (name);
1759 g_hash_table_insert (leaked_entries, GUINT_TO_POINTER (key), path);
1769 * @cache: a #SoupCache
1771 * Loads the contents of @cache's index into memory.
1776 soup_cache_load (SoupCache *cache)
1778 gboolean must_revalidate;
1779 guint32 freshness_lifetime, hits;
1780 guint32 corrected_initial_age, response_time;
1781 char *url, *filename = NULL, *contents = NULL;
1782 GVariant *cache_variant;
1783 GVariantIter *entries_iter = NULL, *headers_iter = NULL;
1785 SoupCacheEntry *entry;
1786 SoupCachePrivate *priv = cache->priv;
1787 guint16 version, status_code;
1788 #if ENABLE(TIZEN_TV_SOUP_CACHE_CLEAN_LEAKED_RESOURCES)
1789 GHashTable *leaked_entries = NULL;
1790 GHashTableIter iter;
1793 #if ENABLE(TIZEN_USER_AGENT_CHECK_IN_CACHE)
1796 filename = g_build_filename (priv->cache_dir, SOUP_CACHE_FILE, NULL);
1797 if (!g_file_get_contents (filename, &contents, &length, NULL)) {
1800 clear_cache_files (cache);
1805 cache_variant = g_variant_new_from_data (G_VARIANT_TYPE (SOUP_CACHE_ENTRIES_FORMAT),
1806 (const char *) contents, length, FALSE, g_free, contents);
1807 g_variant_get (cache_variant, SOUP_CACHE_ENTRIES_FORMAT, &version, &entries_iter);
1808 if (version != SOUP_CACHE_CURRENT_VERSION) {
1809 g_variant_iter_free (entries_iter);
1810 g_variant_unref (cache_variant);
1811 clear_cache_files (cache);
1815 #if ENABLE(TIZEN_TV_SOUP_CACHE_CLEAN_LEAKED_RESOURCES)
1816 leaked_entries = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_free);
1817 #if ENABLE(TIZEN_TV_SOUP_CACHE_OPTIMISE_LOAD_TIME)
1818 soup_cache_foreach_regular_file (cache, (SoupCacheForeachFileFunc)insert_cache_file, leaked_entries);
1820 soup_cache_foreach_file (cache, (SoupCacheForeachFileFunc)insert_cache_file, leaked_entries);
1824 #if ENABLE(TIZEN_USER_AGENT_CHECK_IN_CACHE)
1825 while (g_variant_iter_loop (entries_iter, SOUP_CACHE_PHEADERS_FORMAT,
1826 &url, &must_revalidate, &freshness_lifetime, &corrected_initial_age,
1827 &response_time, &hits, &length, &status_code, &ua,
1830 while (g_variant_iter_loop (entries_iter, SOUP_CACHE_PHEADERS_FORMAT,
1831 &url, &must_revalidate, &freshness_lifetime, &corrected_initial_age,
1832 &response_time, &hits, &length, &status_code,
1835 const char *header_key, *header_value;
1836 SoupMessageHeaders *headers;
1837 SoupMessageHeadersIter soup_headers_iter;
1839 /* SoupMessage Headers */
1840 headers = soup_message_headers_new (SOUP_MESSAGE_HEADERS_RESPONSE);
1841 while (g_variant_iter_loop (headers_iter, SOUP_CACHE_HEADERS_FORMAT, &header_key, &header_value))
1842 if (*header_key && *header_value)
1843 soup_message_headers_append (headers, header_key, header_value);
1845 /* Check that we have headers */
1846 soup_message_headers_iter_init (&soup_headers_iter, headers);
1847 if (!soup_message_headers_iter_next (&soup_headers_iter, &header_key, &header_value)) {
1848 soup_message_headers_free (headers);
1852 /* Insert in cache */
1853 entry = g_slice_new0 (SoupCacheEntry);
1854 entry->uri = g_strdup (url);
1855 entry->must_revalidate = must_revalidate;
1856 entry->freshness_lifetime = freshness_lifetime;
1857 entry->corrected_initial_age = corrected_initial_age;
1858 entry->response_time = response_time;
1860 entry->length = length;
1861 entry->headers = headers;
1862 entry->status_code = status_code;
1863 #if ENABLE(TIZEN_TV_CHECKING_DELETED_ENTRY_FILE)
1864 entry->key = get_cache_key_from_uri ((const char *) entry->uri);
1866 #if ENABLE(TIZEN_TV_SOUP_CACHE_OPTIMISE_LOAD_TIME) && ENABLE(TIZEN_TV_SOUP_CACHE_CLEAN_LEAKED_RESOURCES)
1867 // Check against "leaked_entries" to see if the file exists. This avoids the need to call g_file_query_exists(), which is much slower.
1868 if (g_hash_table_lookup(leaked_entries, GUINT_TO_POINTER(entry->key)) == NULL) {
1869 soup_cache_entry_free (entry);
1873 file = get_file_from_entry (cache, entry);
1875 gboolean file_exist = g_file_query_exists (file, NULL);
1876 g_object_unref(file);
1878 soup_cache_entry_free (entry);
1885 if (!soup_cache_entry_insert (cache, entry, FALSE))
1886 soup_cache_entry_free (entry);
1887 #if ENABLE(TIZEN_TV_SOUP_CACHE_CLEAN_LEAKED_RESOURCES)
1889 g_hash_table_remove (leaked_entries, GUINT_TO_POINTER (entry->key));
1893 /* Remove the leaked files */
1894 #if ENABLE(TIZEN_TV_SOUP_CACHE_CLEAN_LEAKED_RESOURCES)
1895 g_hash_table_iter_init (&iter, leaked_entries);
1896 while (g_hash_table_iter_next (&iter, NULL, &value))
1897 g_unlink ((char *)value);
1898 g_hash_table_destroy (leaked_entries);
1901 cache->priv->lru_start = g_list_reverse (cache->priv->lru_start);
1904 g_variant_iter_free (entries_iter);
1905 g_variant_unref (cache_variant);
1909 * soup_cache_set_max_size:
1910 * @cache: a #SoupCache
1911 * @max_size: the maximum size of the cache, in bytes
1913 * Sets the maximum size of the cache.
1918 soup_cache_set_max_size (SoupCache *cache,
1921 cache->priv->max_size = max_size;
1922 cache->priv->max_entry_data_size = cache->priv->max_size / MAX_ENTRY_DATA_PERCENTAGE;
1926 * soup_cache_get_max_size:
1927 * @cache: a #SoupCache
1929 * Gets the maximum size of the cache.
1931 * Return value: the maximum size of the cache, in bytes.
1936 soup_cache_get_max_size (SoupCache *cache)
1938 return cache->priv->max_size;
1941 #if ENABLE (TIZEN_UPDATE_CACHE_ENTRY_CONTENT_TYPE_HEADER)
1942 void soup_cache_entry_set_content_type (SoupSession *session, SoupMessage *msg, const char *content_type)
1944 SoupCacheEntry *entry;
1945 SoupCache *cache = (SoupCache *)soup_session_get_feature (session, SOUP_TYPE_CACHE);
1947 g_return_if_fail (SOUP_IS_CACHE (cache));
1949 entry = soup_cache_entry_lookup (cache, msg);
1951 soup_message_headers_replace (entry->headers, "Content-Type", content_type);