ftp://ftp.redhat.com/pub/redhat/linux/rawhide/SRPMS/SRPMS/gnome-vfs2-2.3.8-1.src.rpm
[gnome-vfs-httpcaptive.git] / modules / http-cache.c
1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
2 /* http-cache.c - Property caching for DAV
3
4    Copyright (C) 2000-2001 Eazel, Inc.
5
6    The Gnome Library is free software; you can redistribute it and/or
7    modify it under the terms of the GNU Library General Public License as
8    published by the Free Software Foundation; either version 2 of the
9    License, or (at your option) any later version.
10
11    The Gnome Library is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14    Library General Public License for more details.
15
16    You should have received a copy of the GNU Library General Public
17    License along with the Gnome Library; see the file COPYING.LIB.  If not,
18    write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
19    Boston, MA 02111-1307, USA.
20
21    Authors: 
22                  Michael Fleming <mfleming@eazel.com>
23 */
24
25 #include <config.h>
26 #include "http-cache.h"
27
28 #include "http-method.h"
29 #include <glib/ghash.h>
30 #include <glib/gstrfuncs.h>
31 #include <libgnomevfs/gnome-vfs-method.h>
32 #include <libgnomevfs/gnome-vfs-utils.h>
33 #include <string.h>
34
35 /* Cache file info for 5 minutes */
36 #define US_CACHE_FILE_INFO (1000 * 1000 * 60 * 5)
37 /* Cache directory listings for 500 ms */
38 #define US_CACHE_DIRECTORY (1000 * 500)
39
40 /* Mutex for cache data structures */
41 static GStaticRecMutex cache_rlock = G_STATIC_REC_MUTEX_INIT;
42
43 /* Hash maps char * URI ---> FileInfoCacheEntry */
44 static GHashTable * gl_file_info_cache = NULL;
45 /* in-order list of cache entries  for expiration */
46 static GList * gl_file_info_cache_list = NULL;
47 static GList * gl_file_info_cache_list_last = NULL;
48
49 typedef struct {
50         gchar *                 uri_string;
51         GnomeVFSFileInfo *      file_info;
52         utime_t                 create_time;
53         GList *                 my_list_node;   /*node for me in gl_file_info_cache_list*/
54         GList *                 filenames;      /* List of char * basenames for files that are in this 
55                                                  * collection/directory.  Empty for non-directories
56                                                  */
57         gboolean                has_filenames:1;/* For directories, FALSE if the cache does not contain 
58                                                  * the directory's children 
59                                                  */
60         gboolean                is_dav:1;       /* Did this result from a PROPFIND or a GET ? */
61 } FileInfoCacheEntry;
62
63 static FileInfoCacheEntry *     http_cache_entry_new ();
64 static void                     http_cache_entry_free (FileInfoCacheEntry * entry);
65 static FileInfoCacheEntry *     http_cache_add_no_strdup (gchar *uri_string, GnomeVFSFileInfo *file_info, gboolean is_dav);
66 static FileInfoCacheEntry *     http_cache_add (const gchar *uri_string, GnomeVFSFileInfo *file_info, gboolean is_dav);
67
68 void
69 http_cache_init (void)
70 {
71         gl_file_info_cache = g_hash_table_new (g_str_hash, g_str_equal);
72 }
73
74 void
75 http_cache_shutdown (void)
76 {
77         GList *node, *node_next;
78
79         g_static_rec_mutex_lock (&cache_rlock);
80
81         for (   node = g_list_first (gl_file_info_cache_list) ; 
82                 node != NULL; 
83                 node = node_next
84         ) {
85                 node_next = g_list_next (node);
86                 http_cache_entry_free ((FileInfoCacheEntry*) node->data);
87         }
88
89         g_list_free (gl_file_info_cache_list);
90         
91         g_hash_table_destroy (gl_file_info_cache);
92
93         g_static_rec_mutex_unlock (&cache_rlock);
94
95 }
96
97 static FileInfoCacheEntry *
98 http_cache_entry_new (void)
99 {
100         FileInfoCacheEntry *ret;
101
102         g_static_rec_mutex_lock (&cache_rlock);
103
104         ret = g_new0 (FileInfoCacheEntry, 1);
105         ret->create_time = http_util_get_utime();
106         
107         gl_file_info_cache_list = g_list_prepend (gl_file_info_cache_list, ret);
108
109         /* Note that since we've prepended, gl_file_info_cache_list points to us*/
110
111         ret->my_list_node = gl_file_info_cache_list;
112
113         if (gl_file_info_cache_list_last == NULL) {
114                 gl_file_info_cache_list_last = ret->my_list_node;
115         }
116
117         g_static_rec_mutex_unlock (&cache_rlock);
118
119         return ret;
120 }
121
122 /* Warning: as this function removes the cache entry from gl_file_info_cache_list, 
123  * callee's must be careful when calling this during a list iteration
124  */
125 static void
126 http_cache_entry_free (FileInfoCacheEntry * entry)
127 {
128         if (entry) {
129                 GList *node;
130                 
131                 g_static_rec_mutex_lock (&cache_rlock);
132
133                 g_hash_table_remove (gl_file_info_cache, entry->uri_string);
134                 g_free (entry->uri_string);     /* This is the same string as in the hash table */
135                 gnome_vfs_file_info_unref (entry->file_info);
136
137                 if (gl_file_info_cache_list_last == entry->my_list_node) {
138                         gl_file_info_cache_list_last = g_list_previous (entry->my_list_node);
139                 }
140         
141                 gl_file_info_cache_list = g_list_remove_link (gl_file_info_cache_list, entry->my_list_node);
142                 g_list_free_1 (entry->my_list_node);
143
144                 for (node = entry->filenames ; node ; node = g_list_next(node)) {
145                         g_free (node->data);
146                 }
147
148                 g_list_free (entry->filenames); 
149                 
150                 g_free (entry);
151
152                 g_static_rec_mutex_unlock (&cache_rlock);
153         }
154 }
155
156 void
157 http_cache_trim (void)
158 {
159         GList *node, *node_previous;
160         utime_t utime_expire;
161
162         g_static_rec_mutex_lock (&cache_rlock);
163
164         utime_expire = http_util_get_utime() - US_CACHE_FILE_INFO;
165
166         for (   node = gl_file_info_cache_list_last ; 
167                 node && (utime_expire > ((FileInfoCacheEntry *)node->data)->create_time) ;
168                 node = node_previous
169         ) {
170                 node_previous = g_list_previous (node);
171
172                 DEBUG_HTTP (("Cache: Expire: '%s'",((FileInfoCacheEntry *)node->data)->uri_string));
173
174                 http_cache_entry_free ((FileInfoCacheEntry *)(node->data));
175         }
176
177         g_static_rec_mutex_unlock (&cache_rlock);
178 }
179
180 /* Note: doesn't bother trimming entries, so the check can fast */
181 GnomeVFSFileInfo *
182 http_cache_check (const gchar * uri_string)
183 {
184         FileInfoCacheEntry *entry;
185         utime_t utime_expire;
186         GnomeVFSFileInfo *ret;
187
188         g_static_rec_mutex_lock (&cache_rlock);
189
190         utime_expire = http_util_get_utime() - US_CACHE_FILE_INFO;
191
192         entry = (FileInfoCacheEntry *)g_hash_table_lookup (gl_file_info_cache, uri_string);
193
194         if (entry && (utime_expire > entry->create_time)) {
195                 entry = NULL;
196         }
197
198         if (entry) {
199                 gnome_vfs_file_info_ref (entry->file_info);
200
201                 DEBUG_HTTP (("Cache: Hit: '%s'", entry->uri_string));
202
203                 ret = entry->file_info;
204         } else {
205                 ret = NULL;
206         }
207         g_static_rec_mutex_unlock (&cache_rlock);
208         return ret;
209 }
210
211 static gchar *
212 http_cache_uri_to_string  (GnomeVFSURI *uri)
213 {
214         gchar *uri_string;
215         size_t uri_length;
216
217         uri_string = gnome_vfs_uri_to_string (uri,
218                                       GNOME_VFS_URI_HIDE_USER_NAME
219                                       | GNOME_VFS_URI_HIDE_PASSWORD
220                                       | GNOME_VFS_URI_HIDE_TOPLEVEL_METHOD);
221
222         if (uri_string) {
223                 uri_length = strlen (uri_string);
224                 /* Trim off trailing '/'s */
225                 if ( '/' == uri_string[uri_length-1] ) {
226                         uri_string[uri_length-1] = 0;
227                 }
228         }
229
230         return uri_string;
231 }
232
233 GnomeVFSFileInfo *
234 http_cache_check_uri (GnomeVFSURI *uri)
235 {
236         gchar *uri_string;
237         GnomeVFSFileInfo *ret;
238
239         uri_string = http_cache_uri_to_string (uri);
240
241         ret = http_cache_check (uri_string);
242         g_free (uri_string);
243         return ret;
244 }
245
246
247 /* Directory operations demand fresher cache entries */
248 GnomeVFSFileInfo *
249 http_cache_check_directory (const gchar * uri_string, GList **p_child_file_info_list)
250 {
251         FileInfoCacheEntry *entry;
252         utime_t utime_expire;
253         GnomeVFSFileInfo *ret;
254         GList *child_file_info_list = NULL;
255         gboolean cache_incomplete;
256
257         g_static_rec_mutex_lock (&cache_rlock);
258
259         utime_expire = http_util_get_utime() - US_CACHE_DIRECTORY;
260
261         entry = (FileInfoCacheEntry *)g_hash_table_lookup (gl_file_info_cache, uri_string);
262
263         if (entry && (utime_expire > entry->create_time)) {
264                 entry = NULL;
265         }
266
267         if (entry && entry->has_filenames) {
268                 DEBUG_HTTP (("Cache: Hit: '%s'",entry->uri_string));
269
270                 gnome_vfs_file_info_ref (entry->file_info);
271                 ret = entry->file_info;
272         } else {
273                 ret = NULL;
274         }
275
276         if (ret && p_child_file_info_list != NULL) {
277                 GList * filename_node;
278
279                 cache_incomplete = FALSE;
280                 
281                 for (filename_node = entry->filenames ;
282                         filename_node ; 
283                         filename_node = g_list_next (filename_node) 
284                 ) {
285                         char *child_filename;
286                         FileInfoCacheEntry *child_entry;
287
288                         child_filename = g_strconcat (uri_string, "/", (gchar *)filename_node->data, NULL);
289
290                         child_entry = (FileInfoCacheEntry *)g_hash_table_lookup (gl_file_info_cache, child_filename);
291
292                         /* Other HTTP requests on children can cause them to expire before the parent directory */
293                         if (child_entry == NULL) {
294                                 cache_incomplete = TRUE;
295                                 break;
296                         }
297
298                         gnome_vfs_file_info_ref (child_entry->file_info);
299                         child_file_info_list = g_list_prepend (child_file_info_list, child_entry->file_info);
300
301                         g_free (child_filename);
302                 }
303
304                 if (cache_incomplete) {
305                         DEBUG_HTTP (("Cache: Directory was incomplete: '%s'",entry->uri_string));
306
307                         gnome_vfs_file_info_unref (ret);
308                         ret = NULL;
309                         *p_child_file_info_list = NULL;
310                 } else {
311                         *p_child_file_info_list = child_file_info_list;
312                 }
313         }
314
315         g_static_rec_mutex_unlock (&cache_rlock);
316
317         return ret;
318 }
319
320 GnomeVFSFileInfo *
321 http_cache_check_directory_uri (GnomeVFSURI * uri, GList **p_child_file_info_list)
322 {
323         gchar *uri_string;
324         GnomeVFSFileInfo *ret;
325
326         uri_string = http_cache_uri_to_string (uri);
327
328         ret = http_cache_check_directory (uri_string, p_child_file_info_list);
329         g_free (uri_string);
330
331         return ret;
332 }
333
334 /* Note that this neither strdups uri_string nor calls cache_trim() */
335 static FileInfoCacheEntry *
336 http_cache_add_no_strdup (gchar * uri_string, GnomeVFSFileInfo * file_info, gboolean is_dav)
337 {
338         FileInfoCacheEntry *entry_existing;
339         FileInfoCacheEntry *entry;
340
341         g_static_rec_mutex_lock (&cache_rlock);
342
343         entry_existing = (FileInfoCacheEntry *)g_hash_table_lookup (gl_file_info_cache, uri_string);
344
345         DEBUG_HTTP (("Cache: Add: '%s'", uri_string));
346
347         if (entry_existing) {
348                 http_cache_entry_free (entry_existing);
349                 entry_existing = NULL;
350         }
351
352         entry = http_cache_entry_new();
353
354         entry->uri_string =  uri_string; 
355         entry->file_info = file_info;
356         entry->is_dav = is_dav;
357         
358         gnome_vfs_file_info_ref (file_info);
359
360         g_hash_table_insert (gl_file_info_cache, entry->uri_string, entry);
361
362         g_static_rec_mutex_unlock (&cache_rlock);
363
364         return entry;
365 }
366
367 static FileInfoCacheEntry *
368 http_cache_add (const gchar * uri_string, GnomeVFSFileInfo * file_info, gboolean is_dav)
369 {
370         http_cache_trim ();
371         return http_cache_add_no_strdup (g_strdup (uri_string), file_info, is_dav);
372 }
373
374 void
375 http_cache_add_uri_and_children (GnomeVFSURI *uri, GnomeVFSFileInfo *file_info, GList *file_info_list)
376 {
377         gchar *uri_string;
378         gchar *child_string;
379         GList *node;
380         FileInfoCacheEntry *parent_entry;
381
382         http_cache_trim();
383
384         g_static_rec_mutex_lock (&cache_rlock);
385
386         uri_string = http_cache_uri_to_string (uri);
387
388         if (uri_string != NULL) {
389                 /* Note--can't use no_strdup because we use uri_string below */ 
390                 parent_entry = http_cache_add (uri_string, file_info, TRUE);
391
392                 parent_entry->filenames = NULL;
393
394                 for (node = file_info_list ; node != NULL ; node = g_list_next (node)) {
395                         GnomeVFSFileInfo *child_info;
396                         gchar * child_name_escaped;
397
398                         child_info = (GnomeVFSFileInfo *) node->data;
399
400                         child_name_escaped = gnome_vfs_escape_path_string (child_info->name);
401                         
402                         child_string = g_strconcat (uri_string, "/", child_name_escaped, NULL);
403
404                         parent_entry->filenames = g_list_prepend (
405                                                         parent_entry->filenames, 
406                                                         child_name_escaped); 
407                         child_name_escaped = NULL;
408
409                         http_cache_add_no_strdup (child_string, child_info, TRUE);
410                 }
411                 /* I'm not sure that order matters... */
412                 parent_entry->filenames = g_list_reverse (parent_entry->filenames);
413                 parent_entry->has_filenames = TRUE;
414         }
415
416         g_static_rec_mutex_unlock (&cache_rlock);
417
418         g_free (uri_string);
419 }
420
421 void
422 http_cache_add_uri (GnomeVFSURI *uri, GnomeVFSFileInfo *file_info, gboolean is_dav)
423 {
424         http_cache_trim ();
425
426         http_cache_add_no_strdup (http_cache_uri_to_string (uri), file_info, is_dav);
427 }
428
429
430 void
431 http_cache_invalidate (const gchar * uri_string)
432 {
433         FileInfoCacheEntry *entry;
434
435         g_static_rec_mutex_lock (&cache_rlock);
436
437         entry = (FileInfoCacheEntry *)g_hash_table_lookup (gl_file_info_cache, uri_string);
438
439         if (entry) {
440                 DEBUG_HTTP (("Cache: Invalidate: '%s'", entry->uri_string));
441
442                 http_cache_entry_free (entry);
443         }
444
445         g_static_rec_mutex_unlock (&cache_rlock);
446 }
447
448 void
449 http_cache_invalidate_uri (GnomeVFSURI *uri)
450 {
451         gchar *uri_string;
452
453         uri_string = http_cache_uri_to_string (uri);
454
455         if (uri_string) {
456                 http_cache_invalidate (uri_string);
457         }
458
459         g_free (uri_string);
460 }
461
462
463 /* Invalidates entry and everything cached immediately beneath it */
464 void
465 http_cache_invalidate_entry_and_children (const gchar * uri_string)
466 {
467         FileInfoCacheEntry *entry;
468
469         g_static_rec_mutex_lock (&cache_rlock);
470
471         entry = (FileInfoCacheEntry *)g_hash_table_lookup (gl_file_info_cache, uri_string);
472
473         if (entry) {
474                 GList *node;
475                 
476                 DEBUG_HTTP (("Cache: Invalidate Recursive: '%s'", entry->uri_string));
477
478                 for (node = entry->filenames ; node ; node = g_list_next (node) ) {
479                         char *child_filename;
480                         child_filename = g_strconcat (uri_string, "/", (gchar *)node->data, NULL);
481                         http_cache_invalidate (child_filename);
482                         g_free (child_filename);
483                 }
484                 
485                 http_cache_entry_free (entry);
486         }
487
488         g_static_rec_mutex_unlock (&cache_rlock);
489 }
490
491 /* Invalidates entry and everything cached immediately beneath it */
492 void
493 http_cache_invalidate_uri_and_children (GnomeVFSURI *uri)
494 {
495         gchar * uri_string;
496
497         uri_string = http_cache_uri_to_string (uri);
498
499         if (uri_string) {
500                 http_cache_invalidate_entry_and_children (uri_string);
501         }
502
503         g_free (uri_string);
504 }
505
506 /* Invalidate all of this uri's children and all of its parent's children */
507 void
508 http_cache_invalidate_uri_parent (GnomeVFSURI *uri)
509 {
510         gchar * uri_string;
511         gchar * last_slash;
512
513         uri_string = http_cache_uri_to_string (uri);
514
515         if (uri_string) {
516                 http_cache_invalidate_entry_and_children (uri_string);
517
518                 last_slash = strrchr (uri_string, (unsigned char)'/');
519                 if (last_slash) {
520                         *last_slash = 0;
521                         http_cache_invalidate_entry_and_children (uri_string);
522                 }
523         }
524
525         g_free (uri_string);
526 }