ftp://ftp.redhat.com/pub/redhat/linux/rawhide/SRPMS/SRPMS/gnome-vfs2-2.3.8-1.src.rpm
[gnome-vfs-httpcaptive.git] / modules / vfolder / vfolder-common.c
1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
2 /* 
3  * vfolder-common.c - Implementation of abstract Folder, Entry, and Query 
4  *                    interfaces.
5  *
6  * Copyright (C) 2002 Ximian, Inc.
7  *
8  * The Gnome Library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Library General Public License as
10  * published by the Free Software Foundation; either version 2 of the
11  * License, or (at your option) any later version.
12  *
13  * The Gnome Library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * Library General Public License for more details.
17  *
18  * You should have received a copy of the GNU Library General Public
19  * License along with the Gnome Library; see the file COPYING.LIB.  If not,
20  * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
21  * Boston, MA 02111-1307, USA.
22  *
23  * Author: Alex Graveley <alex@ximian.com>
24  *         Based on original code by George Lebl <jirka@5z.com>.
25  */
26
27 #ifdef HAVE_CONFIG_H
28 # include <config.h>
29 #endif
30
31 #include <string.h>
32 #include <sys/time.h>
33
34 #include <glib.h>
35 #include <libgnomevfs/gnome-vfs-directory.h>
36 #include <libgnomevfs/gnome-vfs-ops.h>
37 #include <libgnomevfs/gnome-vfs-xfer.h>
38
39 #include "vfolder-common.h"
40
41 \f
42 /* 
43  * Entry Implementation
44  */
45 Entry *
46 entry_new (VFolderInfo *info, 
47            const gchar *filename, 
48            const gchar *displayname, 
49            gboolean     user_private,
50            gushort      weight)
51 {
52         Entry *entry;
53
54         entry = g_new0 (Entry, 1);
55         entry->refcnt = 1;
56         entry->allocs = 0;
57         entry->info = info;
58         entry->filename = g_strdup (filename);
59         entry->displayname = g_strdup (displayname);
60         entry->user_private = user_private;
61         entry->weight = weight;
62
63         entry->dirty = TRUE;
64         entry->not_shown = FALSE;
65         
66         /* 
67          * Lame-O special case .directory handling, as we don't want them
68          * showing up for all-applications:///.
69          */
70         if (strcmp (displayname, ".directory") != 0)
71                 vfolder_info_add_entry (info, entry);
72
73         return entry;
74 }
75
76 void 
77 entry_ref (Entry *entry)
78 {
79         entry->refcnt++;
80 }
81
82 void 
83 entry_unref (Entry *entry)
84 {
85         entry->refcnt--;
86
87         if (entry->refcnt == 0) {
88                 D (g_print ("-- KILLING ENTRY: (%p) %s---\n",
89                             entry,
90                             entry->displayname));
91
92                 vfolder_info_remove_entry (entry->info, entry);
93
94                 g_free (entry->filename);
95                 g_free (entry->displayname);
96                 g_slist_free (entry->keywords);
97                 g_slist_free (entry->implicit_keywords);
98                 g_free (entry);
99         }
100 }
101
102 void
103 entry_alloc (Entry *entry)
104 {
105         entry->allocs++;
106 }
107
108 void
109 entry_dealloc (Entry *entry)
110 {
111         entry->allocs--;
112 }
113
114 gboolean 
115 entry_is_allocated (Entry *entry)
116 {
117         return entry->allocs > 0;
118 }
119
120 gboolean
121 entry_make_user_private (Entry *entry, Folder *folder)
122 {
123         GnomeVFSURI *src_uri, *dest_uri;
124         GnomeVFSResult result;
125         gchar *uniqname, *filename;
126
127         if (entry->user_private)
128                 return TRUE;
129
130         /* Don't write privately if folder is link */
131         if (folder->is_link)
132                 return TRUE;
133
134         /* Need a writedir, otherwise just modify the original */
135         if (!entry->info->write_dir)
136                 return TRUE;
137
138         /* Need a filename to progress further */
139         if (!entry_get_filename (entry))
140                 return FALSE;
141
142         /* Make sure the destination directory exists */
143         result = vfolder_make_directory_and_parents (entry->info->write_dir, 
144                                                      FALSE, 
145                                                      0700);
146         if (result != GNOME_VFS_OK)
147                 return FALSE;
148
149         /* 
150          * Add a timestamp to the filename since we don't want conflicts between
151          * files in different logical folders with the same filename.
152          */
153         uniqname = vfolder_timestamp_file_name (entry_get_displayname (entry));
154         filename = vfolder_build_uri (entry->info->write_dir, uniqname, NULL);
155         g_free (uniqname);
156
157         src_uri = entry_get_real_uri (entry);
158         dest_uri = gnome_vfs_uri_new (filename);
159
160         result = gnome_vfs_xfer_uri (src_uri, 
161                                      dest_uri, 
162                                      GNOME_VFS_XFER_USE_UNIQUE_NAMES, 
163                                      GNOME_VFS_XFER_ERROR_MODE_ABORT, 
164                                      GNOME_VFS_XFER_OVERWRITE_MODE_ABORT, 
165                                      NULL, 
166                                      NULL);
167
168         gnome_vfs_uri_unref (src_uri);
169         gnome_vfs_uri_unref (dest_uri);
170
171         if (result == GNOME_VFS_OK) {
172                 if (!strcmp (entry_get_displayname (entry), ".directory")) {
173                         folder_set_desktop_file (folder, filename);
174                 } else {
175                         /* Exclude current displayname. */
176                         folder_add_exclude (folder, 
177                                             entry_get_displayname (entry));
178                         /* Remove include for current filename. */
179                         folder_remove_include (folder, 
180                                                entry_get_filename (entry));
181                         /* Add include for new private filename. */
182                         folder_add_include (folder, filename);
183                 }
184
185                 entry_set_filename (entry, filename);
186                 entry_set_weight (entry, 1000);
187                 entry->user_private = TRUE;
188         }
189
190         g_free (filename);
191         
192         return result == GNOME_VFS_OK;
193 }
194
195 gboolean
196 entry_is_user_private (Entry *entry)
197 {
198         return entry->user_private;
199 }
200
201 static void
202 entry_reload_if_needed (Entry *entry)
203 {
204         gboolean changed = FALSE;
205         gchar *keywords, *deprecates, *onlyshowin;
206         int i;
207
208         if (!entry->dirty)
209                 return;
210
211         entry_quick_read_keys (entry, 
212                                "Categories",
213                                &keywords,
214                                "Deprecates",
215                                &deprecates,
216                                "OnlyShowIn",
217                                &onlyshowin);
218
219 #if 0
220         g_printerr ("Read cats=%s onlyshowin=%s from entry %s\n",
221                     keywords ? keywords : "none",
222                     onlyshowin ? onlyshowin : "none",
223                     entry->filename);
224 #endif
225         
226         /* 
227          * Clear keywords from file, leaving only ones added from 
228          * the directory.
229          */
230         g_slist_free (entry->keywords);
231         entry->keywords = g_slist_copy (entry->implicit_keywords);
232
233         if (keywords) {
234                 char **parsed = g_strsplit (keywords, ";", -1);
235                 GSList *keylist = entry->keywords;
236
237                 for (i = 0; parsed[i] != NULL; i++) {
238                         GQuark quark;
239                         const char *word = parsed[i];
240
241                         /* ignore empties (including end of list) */
242                         if (word[0] == '\0')
243                                 continue;
244
245                         quark = g_quark_from_string (word);
246                         if (g_slist_find (keylist, GINT_TO_POINTER (quark)))
247                                 continue;
248
249                         D (g_print ("ADDING KEYWORD: %s, %s\n", 
250                                     entry_get_displayname (entry),
251                                     word));
252
253                         entry->keywords = 
254                                 g_slist_prepend (entry->keywords, 
255                                                  GINT_TO_POINTER (quark));
256                         changed = TRUE;
257                 }
258                 g_strfreev (parsed);
259         }
260
261         /* FIXME: Support this */
262         if (deprecates) {
263                 char **parsed = g_strsplit (keywords, ";", -1);
264                 Entry *dep;
265
266                 for (i = 0; parsed[i] != NULL; i++) {
267                         dep = vfolder_info_lookup_entry (entry->info, 
268                                                          parsed[i]);
269                         if (dep) {
270                                 vfolder_info_remove_entry (entry->info, dep);
271 #if 0 /* vfolder_monitor_emit is not defined */
272                                 vfolder_monitor_emit (
273                                         entry_get_filename (dep),
274                                         GNOME_VFS_MONITOR_EVENT_DELETED);
275 #endif
276                                 entry_unref (dep);
277                         }
278                 }
279                 g_strfreev (parsed);
280         }
281
282         if (onlyshowin) {
283                 char **parsed = g_strsplit (onlyshowin, ";", -1);
284
285                 /* If OnlyShowIn exists, then the default is
286                  * that we don't show the entry, unless the
287                  * value includes "GNOME"
288                  */
289                 entry->not_shown = TRUE;
290                 
291                 for (i = 0; parsed[i] != NULL; i++) {
292                         if (strcmp (parsed[i], "GNOME") == 0)
293                                 entry->not_shown = FALSE;
294                 }
295
296                 /* FIXME do we need to emit some kind of notification here? */
297                 
298                 g_strfreev (parsed);
299         }
300         
301         g_free (keywords);
302         g_free (deprecates);
303         g_free (onlyshowin);
304
305         entry->dirty = FALSE;
306 }
307
308 gushort 
309 entry_get_weight (Entry *entry)
310 {
311         return entry->weight;
312 }
313
314 void
315 entry_set_weight (Entry *entry, gushort weight)
316 {
317         entry->weight = weight;
318 }
319
320 void
321 entry_set_dirty (Entry *entry)
322 {
323         entry->dirty = TRUE;
324 }
325
326 void          
327 entry_set_filename (Entry *entry, const gchar *name)
328 {
329         g_free (entry->filename);
330         entry->filename = g_strdup (name);
331
332         if (entry->uri) {
333                 gnome_vfs_uri_unref (entry->uri);
334                 entry->uri = NULL;
335         }
336
337         entry_set_dirty (entry);
338 }
339
340 const gchar *
341 entry_get_filename (Entry *entry)
342 {
343         return entry->filename;
344 }
345
346 void
347 entry_set_displayname (Entry *entry, const gchar *name)
348 {
349         g_free (entry->displayname);
350         entry->displayname = g_strdup (name);
351 }
352
353 const gchar *
354 entry_get_displayname (Entry *entry)
355 {
356         return entry->displayname;
357 }
358
359 GnomeVFSURI *
360 entry_get_real_uri (Entry *entry)
361 {
362         if (!entry->filename)
363                 return NULL; 
364
365         if (!entry->uri)
366                 entry->uri = gnome_vfs_uri_new (entry->filename);
367
368         gnome_vfs_uri_ref (entry->uri);
369         return entry->uri;
370 }
371
372 const GSList *
373 entry_get_keywords (Entry *entry)
374 {
375         entry_reload_if_needed (entry);
376         return entry->keywords;
377 }
378
379 void 
380 entry_add_implicit_keyword (Entry *entry, GQuark keyword)
381 {
382         entry->keywords = g_slist_prepend (entry->keywords, 
383                                            GINT_TO_POINTER (keyword));
384         entry->implicit_keywords = g_slist_prepend (entry->implicit_keywords, 
385                                                     GINT_TO_POINTER (keyword));
386 }
387
388 static void
389 entry_key_val_from_string (gchar *src, const gchar *key, gchar **result)
390 {
391         gchar *start;
392         gint keylen = strlen (key), end;
393
394         *result = NULL;
395
396         start = strstr (src, key);
397         if (start && 
398             (start == src || (*(start-1) == '\r') || (*(start-1) == '\n')) &&
399             ((*(start+keylen) == ' ') || (*(start+keylen) == '='))) {
400                 start += keylen;
401                 start += strspn (start, "= ");
402                 end = strcspn (start, "\r\n");
403                 if (end > 0)
404                         *result = g_strndup (start, end);
405         }
406 }
407
408 void 
409 entry_quick_read_keys (Entry  *entry,
410                        const gchar  *key1,
411                        gchar       **result1,
412                        const gchar  *key2,
413                        gchar       **result2,
414                        const gchar  *key3,
415                        gchar       **result3)
416 {
417         GnomeVFSHandle *handle;
418         GnomeVFSFileSize readlen;
419         GString *fullbuf;
420         char buf[2048];
421
422         *result1 = NULL;
423         if (key2)
424                 *result2 = NULL;
425         if (key3)
426                 *result3 = NULL;
427
428         if (gnome_vfs_open (&handle, 
429                             entry_get_filename (entry), 
430                             GNOME_VFS_OPEN_READ) != GNOME_VFS_OK)
431                 return;
432
433         fullbuf = g_string_new (NULL);
434         while (gnome_vfs_read (handle, 
435                                buf, 
436                                sizeof (buf), 
437                                &readlen) == GNOME_VFS_OK) {
438                 g_string_append_len (fullbuf, buf, readlen);
439         }
440
441         gnome_vfs_close (handle);
442
443         if (!fullbuf->len) {
444                 g_string_free (fullbuf, TRUE);
445                 return;
446         }
447
448         entry_key_val_from_string (fullbuf->str, key1, result1);
449
450         if (key2)
451                 entry_key_val_from_string (fullbuf->str, key2, result2);
452
453         if (key3)
454                 entry_key_val_from_string (fullbuf->str, key3, result3);
455         
456         g_string_free (fullbuf, TRUE);
457 }
458
459 void
460 entry_dump (Entry *entry, int indent)
461 {
462         gchar *space = g_strnfill (indent, ' ');
463         GSList *keywords = entry->keywords, *iter;
464
465         D (g_print ("%s%s\n%s  Filename: %s\n%s  Keywords: ",
466                     space,
467                     entry_get_displayname (entry),
468                     space,
469                     entry_get_filename (entry),
470                     space));
471
472         for (iter = keywords; iter; iter = iter->next) {
473                 G_GNUC_UNUSED GQuark quark = GPOINTER_TO_INT (iter->data);
474                 D (g_print (g_quark_to_string (quark)));
475         }
476
477         D (g_print ("\n"));
478
479         g_free (space);
480 }
481
482
483 \f
484 /* 
485  * Folder Implementation
486  */
487 Folder *
488 folder_new (VFolderInfo *info, const gchar *name, gboolean user_private)
489 {
490         Folder *folder = g_new0 (Folder, 1);
491
492         folder->name         = g_strdup (name);
493         folder->user_private = user_private;
494         folder->info         = info;
495         folder->refcnt       = 1;
496
497         folder->dirty = TRUE;
498         
499         return folder;
500 }
501
502 void 
503 folder_ref (Folder *folder)
504 {
505         folder->refcnt++;
506 }
507
508 static void
509 unalloc_exclude (gpointer key, gpointer val, gpointer user_data)
510 {
511         gchar *filename = key;
512         VFolderInfo *info = user_data;
513         Entry *entry;
514
515         /* Skip excludes which probably from the parent URI */
516         if (strchr (filename, '/'))
517                 return;
518
519         entry = vfolder_info_lookup_entry (info, filename);
520         if (entry)
521                 entry_dealloc (entry);
522 }
523
524 static void
525 folder_reset_entries (Folder *folder)
526 {
527         /* entries */
528         g_slist_foreach (folder->entries, (GFunc) entry_dealloc, NULL);
529         g_slist_foreach (folder->entries, (GFunc) entry_unref, NULL);
530         g_slist_free (folder->entries);
531         folder->entries = NULL;
532
533         if (folder->entries_ht) {
534                 g_hash_table_destroy (folder->entries_ht);
535                 folder->entries_ht = NULL;
536         }
537 }
538
539 void
540 folder_unref (Folder *folder)
541 {
542         folder->refcnt--;
543
544         if (folder->refcnt == 0) {
545                 D (g_print ("DESTORYING FOLDER: %p, %s\n", 
546                             folder, 
547                             folder->name));
548
549                 g_free (folder->name);
550                 g_free (folder->extend_uri);
551                 g_free (folder->desktop_file);
552
553                 if (folder->extend_monitor)
554                         vfolder_monitor_cancel (folder->extend_monitor);
555
556                 query_free (folder->query);
557
558                 if (folder->excludes) {
559                         g_hash_table_foreach (folder->excludes, 
560                                               (GHFunc) unalloc_exclude,
561                                               folder->info);                    
562                         g_hash_table_destroy (folder->excludes);
563                 }
564
565                 g_slist_foreach (folder->includes, (GFunc) g_free, NULL);
566                 g_slist_free (folder->includes);
567
568                 /* subfolders */
569                 g_slist_foreach (folder->subfolders, 
570                                  (GFunc) folder_unref, 
571                                  NULL);
572                 g_slist_free (folder->subfolders);
573
574                 if (folder->subfolders_ht)
575                         g_hash_table_destroy (folder->subfolders_ht);
576
577                 folder_reset_entries (folder);
578
579                 g_free (folder);
580         }
581 }
582
583 static gboolean read_one_extended_entry (Folder           *folder, 
584                                          const gchar      *file_uri, 
585                                          GnomeVFSFileInfo *file_info);
586
587 static void
588 folder_extend_monitor_cb (GnomeVFSMonitorHandle    *handle,
589                           const gchar              *monitor_uri,
590                           const gchar              *info_uri,
591                           GnomeVFSMonitorEventType  event_type,
592                           gpointer                  user_data)
593 {
594         Folder *folder = user_data;
595         FolderChild child;
596         GnomeVFSFileInfo *file_info;
597         GnomeVFSResult result;
598         GnomeVFSURI *uri, *entry_uri;
599         gchar *filename;
600
601         /* Operating on the whole directory, ignore */
602         if (!strcmp (monitor_uri, info_uri))
603                 return;
604
605         D (g_print ("*** Exdended folder %s ('%s') monitor %s%s%s called! ***\n",
606                     folder->name,
607                     info_uri,
608                     event_type == GNOME_VFS_MONITOR_EVENT_CREATED ? "CREATED":"",
609                     event_type == GNOME_VFS_MONITOR_EVENT_DELETED ? "DELETED":"",
610                     event_type == GNOME_VFS_MONITOR_EVENT_CHANGED ? "CHANGED":""));
611
612         uri = gnome_vfs_uri_new (info_uri);
613         filename = gnome_vfs_uri_extract_short_name (uri);
614
615         VFOLDER_INFO_WRITE_LOCK (folder->info);
616
617         switch (event_type) {
618         case GNOME_VFS_MONITOR_EVENT_CHANGED:
619                 /* 
620                  * We only care about entries here, as the extend_monitor_cb on
621                  * the subfolders themselves should take care of emitting
622                  * changes.
623                  */
624                 child.entry = folder_get_entry (folder, filename);
625                 if (child.entry) {
626                         entry_uri = entry_get_real_uri (child.entry);
627
628                         if (gnome_vfs_uri_equal (entry_uri, uri)) {
629                                 entry_set_dirty (child.entry);
630                                 folder_emit_changed (
631                                         folder, 
632                                         entry_get_displayname (child.entry),
633                                         GNOME_VFS_MONITOR_EVENT_CHANGED);
634                         }
635
636                         gnome_vfs_uri_unref (entry_uri);
637                 }
638                 break;
639         case GNOME_VFS_MONITOR_EVENT_DELETED:
640                 folder_get_child (folder, filename, &child);
641
642                 /* 
643                  * FIXME: should look for replacement in info's entry
644                  * pool here, before sending event 
645                  */
646
647                 if (child.type == DESKTOP_FILE) {
648                         entry_uri = entry_get_real_uri (child.entry);
649
650                         if (gnome_vfs_uri_equal (uri, entry_uri)) {
651                                 folder_remove_entry (folder, child.entry);
652                                 folder_emit_changed (
653                                         folder, 
654                                         filename,
655                                         GNOME_VFS_MONITOR_EVENT_DELETED);
656                         }
657
658                         gnome_vfs_uri_unref (entry_uri);
659                 } 
660                 else if (child.type == FOLDER) {
661                         if (folder_is_user_private (child.folder)) {
662                                 folder_set_dirty (child.folder);
663                         } else {
664                                 folder_remove_subfolder (folder, child.folder);
665                                 folder_emit_changed (
666                                         folder, 
667                                         filename,
668                                         GNOME_VFS_MONITOR_EVENT_DELETED);
669                         }
670                 }
671                 break;
672         case GNOME_VFS_MONITOR_EVENT_CREATED:
673                 file_info = gnome_vfs_file_info_new ();
674                 result = 
675                         gnome_vfs_get_file_info_uri (
676                                 uri,
677                                 file_info,
678                                 GNOME_VFS_FILE_INFO_DEFAULT);
679
680                 if (result == GNOME_VFS_OK &&
681                     read_one_extended_entry (folder, info_uri, file_info))
682                         folder_emit_changed (folder, 
683                                              file_info->name,
684                                              GNOME_VFS_MONITOR_EVENT_CREATED);
685
686                 gnome_vfs_file_info_unref (file_info);
687                 break;
688         default:
689                 break;
690         }
691
692         folder->info->modification_time = time (NULL);
693
694         VFOLDER_INFO_WRITE_UNLOCK (folder->info);
695
696         gnome_vfs_uri_unref (uri);
697         g_free (filename);
698 }
699
700 gboolean
701 folder_make_user_private (Folder *folder)
702 {       
703         if (folder->user_private)
704                 return TRUE;
705
706         if (folder->parent) {
707                 if (folder->parent->read_only ||
708                     !folder_make_user_private (folder->parent))
709                         return FALSE;
710
711                 if (!folder->parent->has_user_private_subfolders) {
712                         Folder *iter;
713
714                         for (iter = folder->parent; iter; iter = iter->parent)
715                                 iter->has_user_private_subfolders = TRUE;
716                 }
717         }
718
719         folder->user_private = TRUE;
720
721         vfolder_info_set_dirty (folder->info);
722
723         return TRUE;
724 }
725
726 gboolean
727 folder_is_user_private (Folder *folder)
728 {
729         return folder->user_private;
730 }
731
732 static gboolean
733 create_dot_directory_entry (Folder *folder)
734 {
735         Entry *entry = NULL, *existing;
736         const gchar *dot_directory = folder_get_desktop_file (folder);
737
738         /* Only replace if existing isn't user-private */
739         existing = folder_get_entry (folder, ".directory");
740         if (existing && entry_get_weight (existing) == 1000)
741                 return FALSE;
742
743         if (strchr (dot_directory, '/')) {
744                 /* Assume full path or URI */
745                 entry = entry_new (folder->info, 
746                                    dot_directory, 
747                                    ".directory", 
748                                    TRUE /*user_private*/,
749                                    950  /*weight*/);
750         } else {
751                 gchar *dirpath = NULL;
752                 gchar *full_path;
753
754                 if (folder->info->desktop_dir)
755                         dirpath = folder->info->desktop_dir;
756                 else if (folder->info->write_dir)
757                         dirpath = folder->info->write_dir;
758                 else
759                         return FALSE;
760
761                 if (dirpath) {
762                         full_path = vfolder_build_uri (dirpath,
763                                                        dot_directory, 
764                                                        NULL);
765                         entry = entry_new (folder->info,
766                                            full_path,
767                                            ".directory",
768                                            TRUE /*user_private*/,
769                                            950  /*weight*/);
770                         g_free (full_path);
771                 }
772         }
773
774         if (entry) {
775                 folder_add_entry (folder, entry);
776                 entry_unref (entry);
777         }
778
779         return entry != NULL;
780 }
781
782 static gboolean
783 read_one_include (Folder *folder, const gchar *file_uri)
784 {
785         Entry *entry = NULL, *existing;
786         GnomeVFSURI *uri;
787         gchar *basename, *basename_ts;
788
789         if (!strchr (file_uri, '/')) {
790                 entry = vfolder_info_lookup_entry (folder->info, file_uri);
791                 if (entry && entry != folder_get_entry (folder, file_uri)) {
792                         folder_add_entry (folder, entry);
793                         return TRUE;
794                 }
795                 return FALSE;
796         }
797         else {
798                 uri = gnome_vfs_uri_new (file_uri);
799                 if (!uri || !gnome_vfs_uri_exists (uri))
800                         return FALSE;
801
802                 basename = gnome_vfs_uri_extract_short_name (uri);
803
804                 /* If including something from the WriteDir, untimestamp it. */
805                 if (folder->info->write_dir &&
806                     strstr (file_uri, folder->info->write_dir)) {
807                         basename_ts = basename;
808                         basename = vfolder_untimestamp_file_name (basename_ts);
809                         g_free (basename_ts);
810                 }
811
812                 /* Only replace if existing is not user-private */
813                 existing = folder_get_entry (folder, basename);
814                 if (existing && entry_get_weight (existing) == 1000) {
815                         gnome_vfs_uri_unref (uri);
816                         g_free (basename);
817                         return FALSE;
818                 }
819
820                 entry = entry_new (folder->info, 
821                                    file_uri,
822                                    basename, 
823                                    TRUE,
824                                    1000 /*weight*/);
825                 folder_add_entry (folder, entry);
826
827                 entry_unref (entry);
828                 gnome_vfs_uri_unref (uri);
829                 g_free (basename);
830
831                 return TRUE;
832         }
833 }
834
835 static gboolean 
836 read_includes (Folder *folder)
837 {
838         GSList *iter;
839         gboolean changed = FALSE;
840
841         for (iter = folder->includes; iter; iter = iter->next) {
842                 gchar *include = iter->data;
843
844                 changed |= read_one_include (folder, include);
845         }
846
847         return changed;
848 }
849
850 static gboolean
851 is_excluded (Folder *folder, const gchar *filename, const gchar *displayname)
852 {
853         if (!folder->excludes)
854                 return FALSE;
855
856         if (displayname && g_hash_table_lookup (folder->excludes, displayname))
857                 return TRUE;
858
859         if (filename && g_hash_table_lookup (folder->excludes, filename))
860                 return TRUE;
861
862         return FALSE;
863 }
864
865 static gboolean
866 read_one_extended_entry (Folder           *folder, 
867                          const gchar      *file_uri, 
868                          GnomeVFSFileInfo *file_info)
869 {
870         Query *query = folder_get_query (folder);
871
872 #if 0
873         g_printerr ("reading one extended entry %s\n", file_uri ? file_uri : "null");
874 #endif
875         
876         if (is_excluded (folder, file_uri, file_info->name))
877                 return FALSE;
878
879         if (file_info->type == GNOME_VFS_FILE_TYPE_DIRECTORY) {
880                 Folder *sub;
881
882                 if (folder_get_subfolder (folder, file_info->name))
883                         return FALSE;
884
885                 sub = folder_new (folder->info, file_info->name, FALSE);
886
887                 folder_set_extend_uri (sub, file_uri);
888                 sub->is_link = folder->is_link;
889
890                 folder_add_subfolder (folder, sub);
891                 folder_unref (sub);
892
893                 return TRUE;
894         } else {
895                 Entry *entry, *existing;
896                 gboolean retval = FALSE;
897
898                 /* Only replace if entry is more important than existing */
899                 existing = folder_get_entry (folder, file_info->name);
900                 if (existing && entry_get_weight (existing) >= 900)
901                         return FALSE;
902
903                 entry = entry_new (folder->info, 
904                                    file_uri,
905                                    file_info->name, 
906                                    FALSE /*user_private*/,
907                                    900   /*weight*/);
908
909                 /* Include unless specifically excluded by query */
910                 if (!query || query_try_match (query, folder, entry)) {
911                         D (g_print ("ADDING EXTENDED ENTRY: "
912                                     "%s, %s, #%d!\n",
913                                     folder_get_name (folder),
914                                     entry_get_displayname (entry),
915                                     g_slist_length ((GSList*)
916                                             folder_list_entries (folder))));
917
918                         folder_add_entry (folder, entry);
919                         retval = TRUE;
920                 }
921
922                 entry_unref (entry);
923                 return retval;
924         }
925 }
926
927 static gboolean
928 read_extended_entries (Folder *folder)
929 {
930         GnomeVFSResult result;
931         GnomeVFSDirectoryHandle *handle;
932         GnomeVFSFileInfo *file_info;
933         const gchar *extend_uri;
934         gboolean changed = FALSE;
935
936         extend_uri = folder_get_extend_uri (folder);
937
938         result = gnome_vfs_directory_open (&handle,
939                                            extend_uri,
940                                            GNOME_VFS_FILE_INFO_DEFAULT);
941
942 #if 0
943         g_printerr ("reading extended entries from %s result = %s\n"<
944                     extend_uri ? extend_uri : "null",
945                     result == GNOME_VFS_OK ? "ok" : "failed");
946 #endif
947         
948         if (result != GNOME_VFS_OK)
949                 return FALSE;
950
951         file_info = gnome_vfs_file_info_new ();
952
953         while (TRUE) {
954                 gchar *file_uri;
955
956                 result = gnome_vfs_directory_read_next (handle, file_info);
957                 if (result != GNOME_VFS_OK)
958                         break;
959
960                 if (!strcmp (file_info->name, ".") ||
961                     !strcmp (file_info->name, ".."))
962                         continue;
963
964                 file_uri = vfolder_build_uri (extend_uri, 
965                                               file_info->name, 
966                                               NULL);
967
968                 changed |= read_one_extended_entry (folder, 
969                                                     file_uri, 
970                                                     file_info);
971
972                 g_free (file_uri);
973         }
974
975         gnome_vfs_file_info_unref (file_info);
976         gnome_vfs_directory_close (handle);
977
978         return changed;
979 }
980
981 static gboolean
982 read_one_info_entry_pool (Folder *folder, Entry *entry)
983 {
984         Query *query = folder_get_query (folder);
985         Entry *existing;
986
987         if (is_excluded (folder, 
988                          entry_get_filename (entry), 
989                          entry_get_displayname (entry))) {
990                 /* 
991                  * Being excluded counts as a ref because we don't want
992                  * them showing up in the Others menu.
993                  */
994                 entry_alloc (entry);
995                 return FALSE;
996         }
997
998         /* Only replace if entry is more important than existing */
999         existing = folder_get_entry (folder, entry_get_displayname (entry));
1000         if (existing && entry_get_weight (existing) >= entry_get_weight (entry))
1001                 return FALSE;
1002
1003         /* Only include if matches a mandatory query. */
1004         if (query && query_try_match (query, folder, entry)) {
1005                 D (g_print ("ADDING POOL ENTRY: %s, %s, #%d!!!!\n",
1006                             folder_get_name (folder),
1007                             entry_get_displayname (entry),
1008                             g_slist_length (
1009                                     (GSList*) folder_list_entries (folder))));
1010
1011                 folder_add_entry (folder, entry);
1012
1013                 return TRUE;
1014         } else
1015                 return FALSE;
1016 }
1017
1018 static gboolean
1019 read_info_entry_pool (Folder *folder)
1020 {
1021         const GSList *all_entries, *iter;
1022         Query *query;
1023         gboolean changed = FALSE;
1024
1025         if (folder->only_unallocated)
1026                 return FALSE;
1027
1028         query = folder_get_query (folder);
1029         all_entries = vfolder_info_list_all_entries (folder->info);
1030
1031         for (iter = all_entries; iter; iter = iter->next) {
1032                 Entry *entry = iter->data;
1033
1034                 changed |= read_one_info_entry_pool (folder, entry);
1035         }
1036
1037         return changed;
1038 }
1039
1040 void
1041 folder_emit_changed (Folder                   *folder,
1042                      const gchar              *child,
1043                      GnomeVFSMonitorEventType  event_type)
1044 {
1045         Folder *iter;
1046         GString *buf;
1047
1048         buf = g_string_new (NULL);
1049
1050         if (child) {
1051                 g_string_prepend (buf, child);
1052                 g_string_prepend_c (buf, '/');
1053         }
1054
1055         for (iter = folder; 
1056              iter != NULL && iter != folder->info->root; 
1057              iter = iter->parent) {
1058                 g_string_prepend (buf, folder_get_name (iter));
1059                 g_string_prepend_c (buf, '/');
1060         }
1061         
1062         vfolder_info_emit_change (folder->info, 
1063                                   buf->len ? buf->str : "/", 
1064                                   event_type);
1065
1066         g_string_free (buf, TRUE);
1067 }
1068
1069 static void
1070 remove_extended_subfolders (Folder *folder)
1071 {
1072         GSList *iter, *copy;
1073         Folder *sub;
1074
1075         copy = g_slist_copy ((GSList *) folder_list_subfolders (folder));
1076         for (iter = copy; iter; iter = iter->next) {
1077                 sub = iter->data;
1078                 if (!folder_is_user_private (sub))
1079                         folder_remove_subfolder (folder, sub);
1080         }
1081         g_slist_free (copy);
1082 }
1083
1084 static void
1085 folder_reload_if_needed (Folder *folder)
1086 {
1087         gboolean changed = FALSE;
1088
1089 #if 0
1090         g_printerr ("folder maybe reload dirty = %d loading = %d\n",
1091                     folder->dirty, folder->loading);
1092 #endif
1093         
1094         if (!folder->dirty || folder->loading)
1095                 return;
1096
1097         D (g_print ("----- RELOADING FOLDER: %s -----\n",
1098                     folder->name));
1099
1100         folder->loading = TRUE;
1101         folder->info->loading = TRUE;
1102
1103         folder_reset_entries (folder);
1104         remove_extended_subfolders (folder);
1105
1106         if (folder_get_desktop_file (folder))
1107                 changed |= create_dot_directory_entry (folder);
1108
1109         if (folder->includes)
1110                 changed |= read_includes (folder);
1111
1112         if (folder_get_extend_uri (folder)) {
1113                 changed |= read_extended_entries (folder);
1114
1115                 /* Start monitoring here, to cut down on unneeded events */
1116                 if (!folder->extend_monitor)
1117                         folder->extend_monitor = 
1118                                 vfolder_monitor_dir_new (
1119                                         folder_get_extend_uri (folder),
1120                                         folder_extend_monitor_cb,
1121                                         folder);
1122         } else {
1123 #if 0
1124                 g_printerr ("folder %s has no extend uri, not reading\n",
1125                             folder->name);
1126 #endif
1127         }
1128
1129         if (folder_get_query (folder))
1130                 changed |= read_info_entry_pool (folder);
1131
1132         if (changed)
1133                 folder_emit_changed (folder, 
1134                                      NULL,
1135                                      GNOME_VFS_MONITOR_EVENT_CHANGED);  
1136
1137         folder->info->loading = FALSE;
1138         folder->loading = FALSE;
1139         folder->dirty = FALSE;
1140 }
1141
1142 void
1143 folder_set_dirty (Folder *folder)
1144 {
1145         folder->dirty = TRUE;
1146 }
1147
1148 void 
1149 folder_set_name (Folder *folder, const gchar *name)
1150 {
1151         g_free (folder->name);
1152         folder->name = g_strdup (name);
1153
1154         vfolder_info_set_dirty (folder->info);
1155 }
1156
1157 const gchar *
1158 folder_get_name (Folder *folder)
1159 {
1160         return folder->name;
1161 }
1162
1163 void
1164 folder_set_query (Folder *folder, Query *query)
1165 {
1166         if (folder->query)
1167                 query_free (folder->query);
1168
1169         folder->query = query;
1170
1171         folder_set_dirty (folder);
1172         vfolder_info_set_dirty (folder->info);
1173 }
1174
1175 Query *
1176 folder_get_query (Folder *folder)
1177 {
1178         return folder->query;
1179 }
1180
1181 void
1182 folder_set_extend_uri (Folder *folder, const gchar *uri)
1183 {
1184 #if 0
1185         g_printerr ("setting extend URI of %s to %s\n",
1186                     folder->name,
1187                     uri ? uri : "null");
1188 #endif
1189         
1190         g_free (folder->extend_uri);
1191         folder->extend_uri = g_strdup (uri);
1192
1193         if (folder->extend_monitor) {
1194                 vfolder_monitor_cancel (folder->extend_monitor);
1195                 folder->extend_monitor = NULL;
1196         }
1197
1198         folder_set_dirty (folder);
1199         vfolder_info_set_dirty (folder->info);
1200 }
1201
1202 const gchar *
1203 folder_get_extend_uri (Folder *folder)
1204 {
1205         return folder->extend_uri;
1206 }
1207
1208 void 
1209 folder_set_desktop_file (Folder *folder, const gchar *filename)
1210 {
1211         g_free (folder->desktop_file);
1212         folder->desktop_file = g_strdup (filename);
1213
1214         vfolder_info_set_dirty (folder->info);
1215 }
1216
1217 const gchar *
1218 folder_get_desktop_file (Folder *folder)
1219 {
1220         return folder->desktop_file;
1221 }
1222
1223 gboolean 
1224 folder_get_child  (Folder *folder, const gchar *name, FolderChild *child)
1225 {
1226         Folder *subdir;
1227         Entry *file;
1228
1229         memset (child, 0, sizeof (FolderChild));
1230
1231         if (name)
1232                 subdir = folder_get_subfolder (folder, name);
1233         else
1234                 /* No name, just return the parent folder */
1235                 subdir = folder;
1236
1237         if (subdir) {
1238                 child->type = FOLDER;
1239                 child->folder = subdir;
1240                 return TRUE;
1241         }
1242
1243         file = folder_get_entry (folder, name);
1244         if (file) {
1245                 child->type = DESKTOP_FILE;
1246                 child->entry = file;
1247                 return TRUE;
1248         }
1249
1250         return FALSE;
1251 }
1252
1253 static void
1254 child_list_foreach_prepend (gpointer key, 
1255                             gpointer val, 
1256                             gpointer user_data)
1257 {
1258         gchar *name = key;
1259         GSList **list = user_data;
1260
1261         *list = g_slist_prepend (*list, g_strdup (name));
1262 }
1263
1264 static GSList * 
1265 child_list_prepend_sorted (gchar      *sortorder, 
1266                            GHashTable *name_hash)
1267 {
1268         GSList *ret = NULL;
1269         gchar **split_ord;
1270         int i;
1271
1272         if (!sortorder)
1273                 return NULL;
1274
1275         split_ord = g_strsplit (sortorder, ":", -1);
1276         if (split_ord && split_ord [0]) {
1277                 for (i = 0; split_ord [i]; i++) {
1278                         gchar *name = split_ord [i];
1279
1280                         if (g_hash_table_lookup (name_hash, name)) {
1281                                 g_hash_table_remove (name_hash, name);
1282                                 ret = g_slist_prepend (ret, g_strdup (name));
1283                         }
1284                 }
1285         }
1286
1287         return ret;
1288 }
1289
1290 GSList *
1291 folder_list_children (Folder *folder)
1292 {       
1293         Entry *dot_directory;
1294         GHashTable *name_hash;
1295         const GSList *iter;
1296         GSList *list = NULL;
1297
1298         /* FIXME: handle duplicate names here, by not using a hashtable */
1299
1300         name_hash = g_hash_table_new (g_str_hash, g_str_equal);
1301
1302         for (iter = folder_list_subfolders (folder); iter; iter = iter->next) {
1303                 Folder *child = iter->data;
1304                 g_hash_table_insert (name_hash, 
1305                                      (gchar *) folder_get_name (child),
1306                                      NULL);
1307         }
1308
1309         for (iter = folder_list_entries (folder); iter; iter = iter->next) {
1310                 Entry *entry = iter->data;
1311                 g_hash_table_insert (name_hash, 
1312                                      (gchar *) entry_get_displayname (entry),
1313                                      NULL);
1314         }
1315
1316         if (folder->only_unallocated) {
1317                 Query *query = folder_get_query (folder);
1318
1319                 iter = vfolder_info_list_all_entries (folder->info);
1320                 for (; iter; iter = iter->next) {
1321                         Entry *entry = iter->data;
1322
1323                         if (entry_is_allocated (entry))
1324                                 continue;
1325
1326                         if (query && !query_try_match (query, folder, entry))
1327                                 continue;
1328
1329                         if (entry->not_shown)
1330                                 continue;
1331                         
1332                         g_hash_table_insert (
1333                                 name_hash, 
1334                                 (gchar *) entry_get_displayname (entry),
1335                                 NULL);
1336                 }
1337         }
1338
1339         dot_directory = folder_get_entry (folder, ".directory");
1340         if (dot_directory) {
1341                 gchar *sortorder;
1342                 entry_quick_read_keys (dot_directory,
1343                                        "SortOrder",
1344                                        &sortorder,
1345                                        NULL, 
1346                                        NULL,
1347                                        NULL,
1348                                        NULL);
1349                 if (sortorder) {
1350                         list = child_list_prepend_sorted (sortorder,
1351                                                           name_hash);
1352                         g_free (sortorder);
1353                 }
1354         }
1355
1356         g_hash_table_foreach (name_hash, 
1357                               (GHFunc) child_list_foreach_prepend,
1358                               &list);
1359         g_hash_table_destroy (name_hash);
1360
1361         list = g_slist_reverse (list);
1362
1363         return list;
1364 }
1365
1366 Entry *
1367 folder_get_entry (Folder *folder, const gchar *filename)
1368 {
1369         Entry *retval = NULL;
1370
1371         folder_reload_if_needed (folder);
1372
1373         if (folder->entries_ht)
1374                 retval = g_hash_table_lookup (folder->entries_ht, filename);
1375
1376         if (!retval && folder->only_unallocated)
1377                 retval = vfolder_info_lookup_entry (folder->info, filename);
1378
1379         return retval;
1380 }
1381
1382 const GSList *
1383 folder_list_entries (Folder *folder)
1384 {
1385         folder_reload_if_needed (folder);
1386
1387         return folder->entries;
1388 }
1389
1390 /* 
1391  * This doesn't set the folder dirty. 
1392  * Use the include/exclude functions for that.
1393  */
1394 void 
1395 folder_remove_entry (Folder *folder, Entry *entry)
1396 {
1397         const gchar *name;
1398         Entry *existing;
1399
1400         if (!folder->entries_ht)
1401                 return;
1402
1403         name = entry_get_displayname (entry);
1404         existing = g_hash_table_lookup (folder->entries_ht, name);
1405         if (existing) {
1406                 g_hash_table_remove (folder->entries_ht, name);
1407                 folder->entries = g_slist_remove (folder->entries, existing);
1408
1409                 entry_dealloc (existing);
1410                 entry_unref (existing);
1411         }
1412 }
1413
1414 /* 
1415  * This doesn't set the folder dirty. 
1416  * Use the include/exclude functions for that.
1417  */
1418 void 
1419 folder_add_entry (Folder *folder, Entry *entry)
1420 {
1421         entry_alloc (entry);
1422         entry_ref (entry);
1423
1424         folder_remove_entry (folder, entry);
1425
1426         if (!folder->entries_ht) 
1427                 folder->entries_ht = g_hash_table_new (g_str_hash, g_str_equal);
1428
1429         g_hash_table_insert (folder->entries_ht, 
1430                              (gchar *) entry_get_displayname (entry),
1431                              entry);
1432         folder->entries = g_slist_append (folder->entries, entry);
1433 }
1434
1435 void
1436 folder_add_include (Folder *folder, const gchar *include)
1437 {
1438         folder_remove_exclude (folder, include);
1439         
1440         folder->includes = g_slist_prepend (folder->includes, 
1441                                             g_strdup (include));
1442
1443         vfolder_info_set_dirty (folder->info);
1444 }
1445
1446 void 
1447 folder_remove_include (Folder *folder, const gchar *file)
1448 {
1449         GSList *li;
1450
1451         if (!folder->includes)
1452                 return;
1453
1454         li = g_slist_find_custom (folder->includes, 
1455                                   file, 
1456                                   (GCompareFunc) strcmp);
1457         if (li) {
1458                 folder->includes = g_slist_delete_link (folder->includes, li);
1459                 vfolder_info_set_dirty (folder->info);
1460         }
1461 }
1462
1463 void
1464 folder_add_exclude (Folder *parent, const gchar *exclude)
1465 {
1466         char *s;
1467
1468         folder_remove_include (parent, exclude);
1469
1470         if (!parent->excludes)
1471                 parent->excludes = 
1472                         g_hash_table_new_full (g_str_hash,
1473                                                g_str_equal,
1474                                                (GDestroyNotify) g_free,
1475                                                NULL);
1476
1477         s = g_strdup (exclude);
1478         g_hash_table_replace (parent->excludes, s, s);
1479
1480         vfolder_info_set_dirty (parent->info);
1481 }
1482
1483 void 
1484 folder_remove_exclude (Folder *folder, const gchar *file)
1485 {
1486         if (!folder->excludes)
1487                 return;
1488
1489         g_hash_table_remove (folder->excludes, file);
1490
1491         vfolder_info_set_dirty (folder->info);
1492 }
1493
1494 Folder *
1495 folder_get_subfolder (Folder *folder, const gchar *name)
1496 {
1497         folder_reload_if_needed (folder);
1498
1499         if (!folder->subfolders_ht)
1500                 return NULL;
1501
1502         return g_hash_table_lookup (folder->subfolders_ht, name);
1503 }
1504
1505 const GSList * 
1506 folder_list_subfolders (Folder *parent)
1507 {
1508         folder_reload_if_needed (parent);
1509
1510         return parent->subfolders;
1511 }
1512
1513 void
1514 folder_remove_subfolder (Folder *parent, Folder *child)
1515 {
1516         const gchar *name;
1517         Folder *existing;
1518
1519         if (!parent->subfolders_ht)
1520                 return;
1521
1522         name = folder_get_name (child);
1523         existing = g_hash_table_lookup (parent->subfolders_ht, name);
1524         if (existing) {
1525                 g_hash_table_remove (parent->subfolders_ht, name);
1526                 parent->subfolders = g_slist_remove (parent->subfolders, 
1527                                                      existing);
1528                 existing->parent = NULL;
1529                 folder_unref (existing);
1530                 vfolder_info_set_dirty (parent->info);
1531         }
1532 }
1533
1534 void
1535 folder_add_subfolder (Folder *parent, Folder *child)
1536 {
1537         if (child->user_private && !parent->has_user_private_subfolders) {
1538                 Folder *iter;
1539                 for (iter = parent; iter != NULL; iter = iter->parent)
1540                         iter->has_user_private_subfolders = TRUE;
1541         }
1542
1543         folder_ref (child);
1544         child->parent = parent;
1545
1546         if (!parent->subfolders_ht)
1547                 parent->subfolders_ht = g_hash_table_new (g_str_hash, 
1548                                                           g_str_equal);
1549         else
1550                 folder_remove_subfolder (parent, child);
1551
1552         g_hash_table_insert (parent->subfolders_ht, 
1553                              (gchar *) folder_get_name (child),
1554                              child);
1555         parent->subfolders = g_slist_append (parent->subfolders, child);
1556
1557         vfolder_info_set_dirty (parent->info);
1558 }
1559
1560 void
1561 folder_dump_tree (Folder *folder, int indent)
1562 {
1563         const GSList *iter;
1564         gchar *space = g_strnfill (indent, ' ');
1565
1566         D (g_print ("%s(%p): %s\n",
1567                     space,
1568                     folder,
1569                     folder ? folder_get_name (folder) : NULL));
1570
1571         g_free (space);
1572
1573         for (iter = folder_list_subfolders (folder); iter; iter = iter->next) {
1574                 Folder *child = iter->data;
1575
1576                 folder_dump_tree (child, indent + 2);
1577         }
1578 }
1579
1580 /* This is a pretty lame hack */
1581 gboolean
1582 folder_is_hidden (Folder *folder)
1583 {
1584         const GSList *iter, *ents;
1585
1586         if (folder->dont_show_if_empty == FALSE)
1587                 return FALSE;
1588
1589         if (folder->only_unallocated) {
1590                 Query *query = folder_get_query (folder);
1591
1592                 iter = vfolder_info_list_all_entries (folder->info);
1593                 for (; iter; iter = iter->next) {
1594                         Entry *entry = iter->data;
1595
1596                         if (entry_is_allocated (entry))
1597                                 continue;
1598
1599                         if (entry->not_shown)
1600                                 continue;
1601                         
1602                         if (query && !query_try_match (query, folder, entry))
1603                                 continue;
1604
1605                         return FALSE;
1606                 }
1607         }
1608
1609         ents = folder_list_entries (folder);
1610         if (ents) {
1611                 /* If there is only one entry, check it is not .directory */
1612                 if (!ents->next) {
1613                         Entry *dot_directory = ents->data;
1614                         const gchar *name;
1615
1616                         name = entry_get_displayname (dot_directory);
1617                         if (strcmp (".directory", name) != 0)
1618                                 return FALSE;
1619                 } else
1620                         return FALSE;
1621         }
1622
1623         for (iter = folder_list_subfolders (folder); iter; iter = iter->next) {
1624                 Folder *child = iter->data;
1625
1626                 if (!folder_is_hidden (child))
1627                         return FALSE;
1628         }
1629
1630         return TRUE;
1631 }
1632
1633
1634 \f
1635 /* 
1636  * Query Implementation
1637  */
1638 Query *
1639 query_new (int type)
1640 {
1641         Query *query;
1642
1643         query = g_new0 (Query, 1);
1644         query->type = type;
1645
1646         return query;
1647 }
1648
1649 void
1650 query_free (Query *query)
1651 {
1652         if (query == NULL)
1653                 return;
1654
1655         if (query->type == QUERY_OR || query->type == QUERY_AND) {
1656                 g_slist_foreach (query->val.queries, 
1657                                  (GFunc) query_free, 
1658                                  NULL);
1659                 g_slist_free (query->val.queries);
1660         }
1661         else if (query->type == QUERY_FILENAME)
1662                 g_free (query->val.filename);
1663
1664         g_free (query);
1665 }
1666
1667 #define INVERT_IF_NEEDED(val) (query->not ? !(val) : (val))
1668
1669 gboolean
1670 query_try_match (Query  *query,
1671                  Folder *folder,
1672                  Entry  *efile)
1673 {
1674         GSList *li;
1675
1676         if (efile->not_shown)
1677                 return FALSE;
1678         
1679         if (query == NULL)
1680                 return TRUE;
1681         
1682         switch (query->type) {
1683         case QUERY_OR:
1684                 for (li = query->val.queries; li != NULL; li = li->next) {
1685                         Query *subquery = li->data;
1686
1687                         if (query_try_match (subquery, folder, efile))
1688                                 return INVERT_IF_NEEDED (TRUE);
1689                 }
1690                 return INVERT_IF_NEEDED (FALSE);
1691         case QUERY_AND:
1692                 for (li = query->val.queries; li != NULL; li = li->next) {
1693                         Query *subquery = li->data;
1694
1695                         if (!query_try_match (subquery, folder, efile))
1696                                 return INVERT_IF_NEEDED (FALSE);
1697                 }
1698                 return INVERT_IF_NEEDED (TRUE);
1699         case QUERY_PARENT:
1700                 {
1701                         const gchar *extend_uri;
1702                         
1703                         /*
1704                          * Check that entry's path starts with that of the
1705                          * folder's extend_uri, so that we know that it matches
1706                          * the parent query. 
1707                          */
1708                         extend_uri = folder_get_extend_uri (folder);
1709                         if (extend_uri &&
1710                             strncmp (entry_get_filename (efile), 
1711                                      extend_uri,
1712                                      strlen (extend_uri)) == 0) 
1713                                 return INVERT_IF_NEEDED (TRUE);
1714                         else
1715                                 return INVERT_IF_NEEDED (FALSE);
1716                 }
1717         case QUERY_KEYWORD:
1718                 { 
1719                         const GSList *keywords;
1720                         GQuark keyword;
1721
1722                         keywords = entry_get_keywords (efile);
1723                         for (; keywords; keywords = keywords->next) {
1724                                 keyword = GPOINTER_TO_INT (keywords->data);
1725                                 if (keyword == query->val.keyword)
1726                                         return INVERT_IF_NEEDED (TRUE);
1727                         }
1728                 }
1729                 return INVERT_IF_NEEDED (FALSE);
1730         case QUERY_FILENAME:
1731                 if (strchr (query->val.filename, '/') &&
1732                     !strcmp (query->val.filename, entry_get_filename (efile)))
1733                         return INVERT_IF_NEEDED (TRUE);
1734                 else if (!strcmp (query->val.filename, 
1735                                   entry_get_displayname (efile)))
1736                         return INVERT_IF_NEEDED (TRUE);
1737                 else
1738                         return INVERT_IF_NEEDED (FALSE);
1739         }
1740
1741         g_assert_not_reached ();
1742         return FALSE;
1743 }