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-info.c.vfolder-hacks
1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
2 /* 
3  * vfolder-info.c - Loading of .vfolder-info files.  External interface 
4  *                  defined in vfolder-common.h
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
33 #include <glib.h>
34 #include <libgnomevfs/gnome-vfs.h>
35 #include <libgnomevfs/gnome-vfs-monitor-private.h>
36 #include <libxml/parser.h>
37 #include <libxml/tree.h>
38 #include <libxml/xmlmemory.h>
39 #include <sys/time.h>
40
41 #include "vfolder-common.h"
42 #include "vfolder-util.h"
43
44 #define DOT_GNOME ".gnome2"
45
46 typedef enum {
47         ITEM_DIR = 1,
48         MERGE_DIR
49 } ItemDirType;
50
51 typedef struct {
52         VFolderInfo    *info;
53         gint            weight;
54         gchar          *uri;
55         GSList         *monitors;
56         ItemDirType     type;
57 } ItemDir;
58
59 /* .vfolder-info format example:
60  * <VFolderInfo>
61  *   <!-- Merge dirs optional -->
62  *   <MergeDir>/etc/X11/applnk</MergeDir>
63  *   <!-- Only specify if it should override standard location -->
64  *   <ItemDir>/usr/share/applications</ItemDir>
65  *   <!-- This is where the .directories are -->
66  *   <DesktopDir>/etc/X11/gnome/vfolders</DesktopDir>
67  *   <!-- Root folder -->
68  *   <Folder>
69  *     <Name>Root</Name>
70  *
71  *     <Include>important.desktop</Include>
72  *
73  *     <!-- Other folders -->
74  *     <Folder>
75  *       <Name>SomeFolder</Name>
76  *       <ParentLink>http:///mywebdav.com/homedir</ParentLink>
77  *     </Folder>
78  *     <Folder>
79  *       <Name>Test_Folder</Name>
80  *       <Parent>file:///a_readonly_path</Parent>
81  *       <!-- could also be absolute -->
82  *       <Desktop>Test_Folder.directory</Desktop>
83  *       <Query>
84  *         <Or>
85  *           <And>
86  *             <Keyword>Application</Keyword>
87  *             <Keyword>Game</Keyword>
88  *           </And>
89  *           <Keyword>Clock</Keyword>
90  *         </Or>
91  *       </Query>
92  *       <Include>somefile.desktop</Include>
93  *       <Include>someotherfile.desktop</Include>
94  *       <Exclude>yetanother.desktop</Exclude>
95  *     </Folder>
96  *   </Folder>
97  * </VFolderInfo>
98  */
99
100 \f
101 /* 
102  * XML VFolder description writing
103  */
104 static void
105 add_xml_tree_from_query (xmlNode *parent, Query *query)
106 {
107         xmlNode *real_parent;
108
109         if (query->not)
110                 real_parent = xmlNewChild (parent /* parent */,
111                                            NULL /* ns */,
112                                            "Not" /* name */,
113                                            NULL /* content */);
114         else
115                 real_parent = parent;
116
117         if (query->type == QUERY_KEYWORD) {
118                 const char *string = g_quark_to_string (query->val.keyword);
119
120                 xmlNewChild (real_parent /* parent */,
121                              NULL /* ns */,
122                              "Keyword" /* name */,
123                              string /* content */);
124         } else if (query->type == QUERY_FILENAME) {
125                 xmlNewChild (real_parent /* parent */,
126                              NULL /* ns */,
127                              "Filename" /* name */,
128                              query->val.filename /* content */);
129         } else if (query->type == QUERY_PARENT) {
130                 xmlNewChild (real_parent   /* parent */,
131                              NULL          /* ns */,
132                              "ParentQuery" /* name */,
133                              NULL          /* content */);
134         } else if (query->type == QUERY_OR ||
135                    query->type == QUERY_AND) {
136                 xmlNode *node;
137                 const char *name;
138                 GSList *li;
139
140                 if (query->type == QUERY_OR)
141                         name = "Or";
142                 else /* QUERY_AND */
143                         name = "And";
144
145                 node = xmlNewChild (real_parent /* parent */,
146                                     NULL /* ns */,
147                                     name /* name */,
148                                     NULL /* content */);
149
150                 for (li = query->val.queries; li != NULL; li = li->next) {
151                         Query *subquery = li->data;
152                         add_xml_tree_from_query (node, subquery);
153                 }
154         } else {
155                 g_assert_not_reached ();
156         }
157 }
158
159 static void
160 add_excludes_to_xml (gpointer key, gpointer value, gpointer user_data)
161 {
162         const char *filename = key;
163         xmlNode *folder_node = user_data;
164
165         xmlNewChild (folder_node /* parent */,
166                      NULL /* ns */,
167                      "Exclude" /* name */,
168                      filename /* content */);
169 }
170
171 static void
172 add_xml_tree_from_folder (xmlNode *parent, Folder *folder)
173 {
174         const GSList *li;
175         xmlNode *folder_node;
176         const gchar *extend_uri;
177
178         /* 
179          * return if this folder hasn't been modified by the user, 
180          * and contains no modified subfolders.
181          */
182         if (!folder->user_private && !folder->has_user_private_subfolders)
183                 return;
184
185         folder_node = xmlNewChild (parent /* parent */,
186                                    NULL /* ns */,
187                                    "Folder" /* name */,
188                                    NULL /* content */);
189
190         xmlNewChild (folder_node /* parent */,
191                      NULL /* ns */,
192                      "Name" /* name */,
193                      folder_get_name (folder) /* content */);
194
195         extend_uri = folder_get_extend_uri (folder);
196         if (extend_uri) {
197                 xmlNewChild (folder_node /* parent */,
198                              NULL /* ns */,
199                              folder->is_link ? "ParentLink" : "Parent",
200                              extend_uri /* content */);
201         }
202
203         if (folder->user_private) {
204                 const gchar *desktop_file;
205
206                 if (folder->read_only)
207                         xmlNewChild (folder_node /* parent */,
208                                      NULL /* ns */,
209                                      "ReadOnly" /* name */,
210                                      NULL /* content */);
211                 if (folder->dont_show_if_empty)
212                         xmlNewChild (folder_node /* parent */,
213                                      NULL /* ns */,
214                                      "DontShowIfEmpty" /* name */,
215                                      NULL /* content */);
216                 if (folder->only_unallocated)
217                         xmlNewChild (folder_node /* parent */,
218                                      NULL /* ns */,
219                                      "OnlyUnallocated" /* name */,
220                                      NULL /* content */);
221
222                 if (folder->desktop_file != NULL) {
223                         desktop_file = folder_get_desktop_file (folder);
224                         if (desktop_file)
225                                 xmlNewChild (folder_node /* parent */,
226                                              NULL /* ns */,
227                                              "Desktop" /* name */,
228                                              desktop_file);
229                 }
230
231                 for (li = folder->includes; li != NULL; li = li->next) {
232                         const char *include = li->data;
233                         xmlNewChild (folder_node /* parent */,
234                                      NULL /* ns */,
235                                      "Include" /* name */,
236                                      include /* content */);
237                 }
238
239                 if (folder->excludes) {
240                         g_hash_table_foreach (folder->excludes,
241                                               add_excludes_to_xml,
242                                               folder_node);
243                 }
244
245                 if (folder->query) {
246                         xmlNode *query_node;
247                         query_node = xmlNewChild (folder_node /* parent */,
248                                                   NULL /* ns */,
249                                                   "Query" /* name */,
250                                                   NULL /* content */);
251                         add_xml_tree_from_query (query_node, 
252                                                  folder_get_query (folder));
253                 }
254         }
255
256         for (li = folder_list_subfolders (folder); li != NULL; li = li->next) {
257                 Folder *subfolder = li->data;
258                 add_xml_tree_from_folder (folder_node, subfolder);
259         }
260 }
261
262 static xmlDoc *
263 xml_tree_from_vfolder (VFolderInfo *info)
264 {
265         xmlDoc *doc;
266         xmlNode *topnode;
267         GSList *li;
268
269         doc = xmlNewDoc ("1.0");
270
271         topnode = xmlNewDocNode (doc /* doc */,
272                                  NULL /* ns */,
273                                  "VFolderInfo" /* name */,
274                                  NULL /* content */);
275         doc->xmlRootNode = topnode;
276
277         if (info->write_dir != NULL) {
278                 xmlNewChild (topnode /* parent */,
279                              NULL /* ns */,
280                              "WriteDir" /* name */,
281                              info->write_dir /* content */);
282         }
283
284         /* Deprecated */
285         if (info->desktop_dir != NULL) {
286                 xmlNewChild (topnode /* parent */,
287                              NULL /* ns */,
288                              "DesktopDir" /* name */,
289                              info->desktop_dir /* content */);
290         }
291         
292         for (li = info->item_dirs; li != NULL; li = li->next) {
293                 ItemDir *item_dir = li->data;
294
295                 switch (item_dir->type) {
296                 case MERGE_DIR:
297                         xmlNewChild (topnode /* parent */,
298                                      NULL /* ns */,
299                                      "MergeDir" /* name */,
300                                      item_dir->uri /* content */);
301                         break;
302                 case ITEM_DIR:
303                         xmlNewChild (topnode /* parent */,
304                                      NULL /* ns */,
305                                      "ItemDir" /* name */,
306                                      item_dir->uri /* content */);
307                         break;
308                 }
309         }
310
311         if (info->root != NULL)
312                 add_xml_tree_from_folder (topnode, info->root);
313
314         return doc;
315 }
316
317 /* FIXME: what to do about errors */
318 void
319 vfolder_info_write_user (VFolderInfo *info)
320 {
321         xmlDoc *doc;
322         GnomeVFSResult result;
323         gchar *tmpfile;
324         struct timeval tv;
325
326         if (info->loading || !info->dirty)
327                 return;
328
329         if (!info->filename)
330                 return;
331
332         info->loading = TRUE;
333
334         /* FIXME: errors, anyone? */
335         result = vfolder_make_directory_and_parents (info->filename, 
336                                                      TRUE, 
337                                                      0700);
338         if (result != GNOME_VFS_OK) {
339                 g_warning ("Unable to create parent directory for "
340                            "vfolder-info file: %s",
341                            info->filename);
342                 return;
343         }
344
345         doc = xml_tree_from_vfolder (info);
346         if (!doc)
347                 return;
348
349         gettimeofday (&tv, NULL);
350         tmpfile = g_strdup_printf ("%s.tmp-%d", 
351                                    info->filename,
352                                    (int) (tv.tv_sec ^ tv.tv_usec));
353
354         /* Write to temporary file */
355         xmlSaveFormatFile (tmpfile, doc, TRUE /* format */);
356
357         /* Avoid being notified of move, since we're performing it */
358         if (info->filename_monitor)
359                 vfolder_monitor_freeze (info->filename_monitor);
360
361         /* Move temp file over to real filename */
362         result = gnome_vfs_move (tmpfile, 
363                                  info->filename, 
364                                  TRUE /*force_replace*/);
365         if (result != GNOME_VFS_OK) {
366                 g_warning ("Error writing vfolder configuration "
367                            "file \"%s\": %s.",
368                            info->filename,
369                            gnome_vfs_result_to_string (result));
370         }
371
372         /* Start listening to changes again */
373         if (info->filename_monitor)
374                 vfolder_monitor_thaw (info->filename_monitor);
375
376         xmlFreeDoc(doc);
377         g_free (tmpfile);
378
379         info->modification_time = time (NULL);
380         info->dirty = FALSE;
381         info->loading = FALSE;
382 }
383
384 \f
385 /* 
386  * XML VFolder description reading
387  */
388 static Query *
389 single_query_read (xmlNode *qnode)
390 {
391         Query *query;
392         xmlNode *node;
393
394         if (qnode->type != XML_ELEMENT_NODE || qnode->name == NULL)
395                 return NULL;
396
397         query = NULL;
398
399         if (g_ascii_strcasecmp (qnode->name, "Not") == 0 &&
400             qnode->xmlChildrenNode != NULL) {
401                 xmlNode *iter;
402
403                 for (iter = qnode->xmlChildrenNode;
404                      iter != NULL && query == NULL;
405                      iter = iter->next)
406                         query = single_query_read (iter);
407                 if (query != NULL) {
408                         query->not = ! query->not;
409                 }
410                 return query;
411         } 
412         else if (g_ascii_strcasecmp (qnode->name, "Keyword") == 0) {
413                 xmlChar *word = xmlNodeGetContent (qnode);
414
415                 if (word != NULL) {
416                         query = query_new (QUERY_KEYWORD);
417                         query->val.keyword = g_quark_from_string (word);
418                         xmlFree (word);
419                 }
420                 return query;
421         } 
422         else if (g_ascii_strcasecmp (qnode->name, "Filename") == 0) {
423                 xmlChar *file = xmlNodeGetContent (qnode);
424
425                 if (file != NULL) {
426                         query = query_new (QUERY_FILENAME);
427                         query->val.filename = g_strdup (file);
428                         xmlFree (file);
429                 }
430                 return query;
431         } 
432         else if (g_ascii_strcasecmp (qnode->name, "ParentQuery") == 0) {
433                 query = query_new (QUERY_PARENT);
434         }
435         else if (g_ascii_strcasecmp (qnode->name, "And") == 0) {
436                 query = query_new (QUERY_AND);
437         } 
438         else if (g_ascii_strcasecmp (qnode->name, "Or") == 0) {
439                 query = query_new (QUERY_OR);
440         } 
441         else {
442                 /* We don't understand */
443                 return NULL;
444         }
445
446         /* This must be OR or AND */
447         g_assert (query != NULL);
448
449         for (node = qnode->xmlChildrenNode; node; node = node->next) {
450                 Query *new_query = single_query_read (node);
451
452                 if (new_query != NULL)
453                         query->val.queries = 
454                                 g_slist_prepend (query->val.queries, new_query);
455         }
456
457         query->val.queries = g_slist_reverse (query->val.queries);
458
459         return query;
460 }
461
462 static void
463 add_or_set_query (Query **query, Query *new_query)
464 {
465         if (*query == NULL) {
466                 *query = new_query;
467         } else {
468                 Query *old_query = *query;
469                 *query = query_new (QUERY_OR);
470                 (*query)->val.queries = 
471                         g_slist_append ((*query)->val.queries, old_query);
472                 (*query)->val.queries = 
473                         g_slist_append ((*query)->val.queries, new_query);
474         }
475 }
476
477 static Query *
478 query_read (xmlNode *qnode)
479 {
480         Query *query;
481         xmlNode *node;
482
483         query = NULL;
484
485         for (node = qnode->xmlChildrenNode; node != NULL; node = node->next) {
486                 if (node->type != XML_ELEMENT_NODE ||
487                     node->name == NULL)
488                         continue;
489
490                 if (g_ascii_strcasecmp (node->name, "Not") == 0 &&
491                     node->xmlChildrenNode != NULL) {
492                         xmlNode *iter;
493                         Query *new_query = NULL;
494
495                         for (iter = node->xmlChildrenNode;
496                              iter != NULL && new_query == NULL;
497                              iter = iter->next)
498                                 new_query = single_query_read (iter);
499                         if (new_query != NULL) {
500                                 new_query->not = ! new_query->not;
501                                 add_or_set_query (&query, new_query);
502                         }
503                 } else {
504                         Query *new_query = single_query_read (node);
505                         if (new_query != NULL)
506                                 add_or_set_query (&query, new_query);
507                 }
508         }
509
510         return query;
511 }
512
513 static Folder *
514 folder_read (VFolderInfo *info, gboolean user_private, xmlNode *fnode)
515 {
516         Folder *folder;
517         xmlNode *node;
518
519         folder = folder_new (info, NULL, user_private);
520
521         for (node = fnode->xmlChildrenNode; node != NULL; node = node->next) {
522                 if (node->type != XML_ELEMENT_NODE ||
523                     node->name == NULL)
524                         continue;
525
526                 if (g_ascii_strcasecmp (node->name, "Name") == 0) {
527                         xmlChar *name = xmlNodeGetContent (node);
528
529                         if (name) {
530                                 g_free (folder->name);
531                                 folder_set_name (folder, name);
532                                 xmlFree (name);
533                         }
534                 } 
535                 else if (g_ascii_strcasecmp (node->name, "Parent") == 0) {
536                         xmlChar *parent = xmlNodeGetContent (node);
537
538                         if (parent) {
539                                 gchar *esc_parent;
540
541                                 esc_parent = vfolder_escape_home (parent);
542                                 folder_set_extend_uri (folder, esc_parent);
543                                 folder->is_link = FALSE;
544
545                                 xmlFree (parent);
546                                 g_free (esc_parent);
547                         }
548                 } 
549                 else if (g_ascii_strcasecmp (node->name, "ParentLink") == 0) {
550                         xmlChar *parent = xmlNodeGetContent (node);
551
552                         if (parent) {
553                                 gchar *esc_parent;
554
555                                 esc_parent = vfolder_escape_home (parent);
556                                 folder_set_extend_uri (folder, esc_parent);
557                                 folder->is_link = TRUE;
558                                 
559                                 xmlFree (parent);
560                                 g_free (esc_parent);
561                         }
562                 } 
563                 else if (g_ascii_strcasecmp (node->name, "Desktop") == 0) {
564                         xmlChar *desktop = xmlNodeGetContent (node);
565
566                         if (desktop) {
567                                 folder_set_desktop_file (folder, desktop);
568                                 xmlFree (desktop);
569                         }
570                 } 
571                 else if (g_ascii_strcasecmp (node->name, "Include") == 0) {
572                         xmlChar *file = xmlNodeGetContent (node);
573
574                         if (file) {
575                                 gchar *esc_file;
576
577                                 esc_file = vfolder_escape_home (file);
578                                 folder_add_include (folder, esc_file);
579
580                                 xmlFree (file);
581                                 g_free (esc_file);
582                         }
583                 }
584                 else if (g_ascii_strcasecmp (node->name, "Exclude") == 0) {
585                         xmlChar *file = xmlNodeGetContent (node);
586
587                         if (file) {
588                                 gchar *esc_file;
589
590                                 esc_file = vfolder_escape_home (file);
591                                 folder_add_exclude (folder, esc_file);
592
593                                 xmlFree (file);
594                                 g_free (esc_file);
595                         }
596                 } 
597                 else if (g_ascii_strcasecmp (node->name, "Query") == 0) {
598                         Query *query;
599
600                         query = query_read (node);
601                         if (query)
602                                 folder_set_query (folder, query);
603                 } 
604                 else if (g_ascii_strcasecmp (node->name, "Folder") == 0) {
605                         Folder *new_folder = folder_read (info, 
606                                                           user_private,
607                                                           node);
608
609                         if (new_folder != NULL) {
610                                 folder_add_subfolder (folder, new_folder);
611                                 folder_unref (new_folder);
612                         }
613                 } 
614                 else if (g_ascii_strcasecmp (node->name, 
615                                              "OnlyUnallocated") == 0) {
616                         folder->only_unallocated = TRUE;
617                         info->has_unallocated_folder = TRUE;
618                 } 
619                 else if (g_ascii_strcasecmp (node->name, "ReadOnly") == 0) {
620                         folder->read_only = TRUE;
621                 } 
622                 else if (g_ascii_strcasecmp (node->name,
623                                              "DontShowIfEmpty") == 0) {
624                         folder->dont_show_if_empty = TRUE;
625                 }
626         }
627
628         /* Name is required */
629         if (!folder_get_name (folder)) {
630                 folder_unref (folder);
631                 return NULL;
632         }
633
634         return folder;
635 }
636
637 static void itemdir_monitor_cb (GnomeVFSMonitorHandle    *handle,
638                                 const gchar              *monitor_uri,
639                                 const gchar              *info_uri,
640                                 GnomeVFSMonitorEventType  event_type,
641                                 gpointer                  user_data);
642
643 static void writedir_monitor_cb (GnomeVFSMonitorHandle    *handle,
644                                  const gchar              *monitor_uri,
645                                  const gchar              *info_uri,
646                                  GnomeVFSMonitorEventType  event_type,
647                                  gpointer                  user_data);
648
649 static void desktopdir_monitor_cb (GnomeVFSMonitorHandle    *handle,
650                                    const gchar              *monitor_uri,
651                                    const gchar              *info_uri,
652                                    GnomeVFSMonitorEventType  event_type,
653                                    gpointer                  user_data);
654
655
656 static char *
657 remove_double_slashes (const char *uri)
658 {
659         const char *src;
660         char *dest;
661         char *result;
662         gboolean slash;
663
664         if (uri == NULL) {
665                 return NULL;
666         }
667
668         result = malloc (strlen (uri) + 1);
669         if (result == NULL) {
670                 return NULL;
671         }
672
673         src = uri;
674         dest = result;
675         slash = FALSE;
676
677         while (*src != '\0') {
678                 /* Don't do anything if current char is a / and slash is TRUE*/
679                 if ((*src == '/') && (slash != FALSE)) {
680                         src++;
681                         continue;
682                 }
683
684                 if ((*src == '/') && (slash == FALSE)) {
685                         slash = TRUE;
686
687                 } else {
688                         slash = FALSE;
689                 }
690
691                 *dest = *src;
692                 dest++;
693                 src++;
694         }
695         *dest = '\0';
696
697         return result;
698 }
699
700 static ItemDir *
701 itemdir_new (VFolderInfo *info, 
702              const gchar *uri, 
703              ItemDirType  type,
704              gint         weight)
705 {
706         ItemDir *ret;
707         gchar *tmp_uri;
708
709         ret = g_new0 (ItemDir, 1);
710         ret->info   = info;
711         ret->weight = weight;
712         tmp_uri = vfolder_escape_home (uri);
713         ret->uri    = remove_double_slashes (tmp_uri);
714         g_free (tmp_uri);
715         ret->type   = type;
716
717         info->item_dirs = g_slist_append (info->item_dirs, ret);
718
719         return ret;
720 }
721
722 static void
723 itemdir_free (ItemDir *itemdir)
724 {
725         GSList *iter;
726
727         for (iter = itemdir->monitors; iter; iter = iter->next) {
728                 VFolderMonitor *monitor = iter->data;
729                 vfolder_monitor_cancel (monitor);
730         }
731
732         g_slist_free (itemdir->monitors);
733         g_free (itemdir->uri);
734         g_free (itemdir);
735 }
736
737 static gboolean
738 read_vfolder_from_file (VFolderInfo     *info,
739                         const gchar     *filename,
740                         gboolean         user_private,
741                         GnomeVFSResult  *result,
742                         GnomeVFSContext *context)
743 {
744         xmlDoc *doc;
745         xmlNode *node;
746         GnomeVFSResult my_result;
747         gint weight = 700;
748
749         if (result == NULL)
750                 result = &my_result;
751
752         /* Fail silently if filename does not exist */
753         if (access (filename, F_OK) != 0)
754                 return TRUE;
755
756         doc = xmlParseFile (filename); 
757         if (doc == NULL
758             || doc->xmlRootNode == NULL
759             || doc->xmlRootNode->name == NULL
760             || g_ascii_strcasecmp (doc->xmlRootNode->name, 
761                                    "VFolderInfo") != 0) {
762                 *result = GNOME_VFS_ERROR_WRONG_FORMAT;
763                 xmlFreeDoc(doc);
764                 return FALSE;
765         }
766
767         if (context != NULL && 
768             gnome_vfs_context_check_cancellation (context)) {
769                 xmlFreeDoc(doc);
770                 *result = GNOME_VFS_ERROR_CANCELLED;
771                 return FALSE;
772         }
773
774         for (node = doc->xmlRootNode->xmlChildrenNode; 
775              node != NULL; 
776              node = node->next) {
777                 if (node->type != XML_ELEMENT_NODE ||
778                     node->name == NULL)
779                         continue;
780
781                 if (context != NULL && 
782                     gnome_vfs_context_check_cancellation (context)) {
783                         xmlFreeDoc(doc);
784                         *result = GNOME_VFS_ERROR_CANCELLED;
785                         return FALSE;
786                 }
787
788                 if (g_ascii_strcasecmp (node->name, "MergeDir") == 0) {
789                         xmlChar *dir = xmlNodeGetContent (node);
790
791                         if (dir != NULL) {
792                                 itemdir_new (info, dir, MERGE_DIR, weight--);
793                                 xmlFree (dir);
794                         }
795                 } 
796                 else if (g_ascii_strcasecmp (node->name, "ItemDir") == 0) {
797                         xmlChar *dir = xmlNodeGetContent (node);
798
799                         if (dir != NULL) {
800                                 itemdir_new (info, dir, ITEM_DIR, weight--);
801                                 xmlFree (dir);
802                         }
803                 } 
804                 else if (g_ascii_strcasecmp (node->name, "WriteDir") == 0) {
805                         xmlChar *dir = xmlNodeGetContent (node);
806
807                         if (dir != NULL) {
808                                 g_free (info->write_dir);
809                                 info->write_dir = vfolder_escape_home (dir);
810                                 xmlFree (dir);
811                         }
812                 } 
813                 else if (g_ascii_strcasecmp (node->name, "DesktopDir") == 0) {
814                         xmlChar *dir = xmlNodeGetContent (node);
815
816                         if (dir != NULL) {
817                                 g_free (info->desktop_dir);
818                                 info->desktop_dir = vfolder_escape_home (dir);
819                                 xmlFree (dir);
820                         }
821                 } 
822                 else if (g_ascii_strcasecmp (node->name, "Folder") == 0) {
823                         Folder *folder = folder_read (info, 
824                                                       user_private,
825                                                       node);
826
827                         if (folder != NULL) {
828                                 if (info->root != NULL)
829                                         folder_unref (info->root);
830
831                                 info->root = folder;
832                         }
833                 } 
834                 else if (g_ascii_strcasecmp (node->name, "ReadOnly") == 0) {
835                         info->read_only = TRUE;
836                 }
837         }
838
839         xmlFreeDoc(doc);
840
841         return TRUE;
842 }
843
844 \f
845 /*
846  * MergeDir/ItemDir entry pool reading 
847  */
848 struct {
849         const gchar *dirname;
850         const gchar *keyword;
851 } mergedir_keywords[] = {
852          /*Parent Dir*/  /*Keyword to add*/
853
854         /* Gnome Menus */
855         { "Development",  "Development" },
856         { "Editors",      "TextEditor" },
857         { "Games",        "Game" },
858         { "Graphics",     "Graphics" },
859         { "Internet",     "Network" },
860         { "Multimedia",   "AudioVideo" },
861         { "Office",       "Office" },
862         { "Settings",     "Settings" },
863         { "System",       "System" },
864         { "Utilities",    "Utility" },
865
866         /* Ximian Menus */
867         { "Addressbook",  "Office" },
868         { "Audio",        "AudioVideo" },
869         { "Calendar",     "Office" },
870         { "Finance",      "Office" },
871
872         /* KDE Menus */
873         { "WordProcessing", "Office" },
874         { "Toys",           "Utility" },
875 };
876
877 static GQuark
878 get_mergedir_keyword (const gchar *dirname)
879 {
880         gint i;
881
882         for (i = 0; i < G_N_ELEMENTS (mergedir_keywords); i++) {
883                 if (g_ascii_strcasecmp (mergedir_keywords [i].dirname, 
884                                         dirname) == 0) {
885                         return g_quark_from_static_string (
886                                         mergedir_keywords [i].keyword);
887                 }
888         }
889
890         return 0;
891 }
892
893 static Entry *
894 create_itemdir_entry (ItemDir          *id, 
895                       const gchar      *rel_path,
896                       GnomeVFSFileInfo *file_info)
897 {
898         Entry *new_entry = NULL;
899         gchar *file_uri;
900         
901         if (!vfolder_check_extension (file_info->name, ".desktop")) 
902                 return NULL;
903
904         if (vfolder_info_lookup_entry (id->info, file_info->name)) {
905                 D (g_print ("EXCLUDING DUPLICATE ENTRY: %s\n", 
906                             file_info->name));
907                 return NULL;
908         }
909
910         file_uri = vfolder_build_uri (id->uri, rel_path, NULL);
911
912         /* Ref belongs to the VFolderInfo */
913         new_entry = entry_new (id->info, 
914                                file_uri        /*filename*/, 
915                                file_info->name /*displayname*/, 
916                                FALSE           /*user_private*/,
917                                id->weight      /*weight*/);
918
919         g_free (file_uri);
920
921         return new_entry;
922 }
923
924 static void
925 add_keywords_from_relative_path (Entry *new_entry, const gchar *rel_path)
926 {
927         gchar **pelems;
928         GQuark keyword;
929         gint i;
930
931         pelems = g_strsplit (rel_path, "/", -1);
932         if (!pelems)
933                 return;
934
935         for (i = 0; pelems [i]; i++) {
936                 keyword = get_mergedir_keyword (pelems [i]);
937                 if (keyword)
938                         entry_add_implicit_keyword (new_entry, keyword);
939         }
940
941         g_strfreev (pelems);
942 }
943
944 static void
945 set_mergedir_entry_keywords (Entry *new_entry, const gchar *rel_path)
946 {
947         static GQuark merged = 0, application = 0, core_quark = 0;
948
949         if (!merged) {
950                 merged = g_quark_from_static_string ("Merged");
951                 application = g_quark_from_static_string("Application");
952                 core_quark = g_quark_from_static_string ("Core");
953         }
954
955         /* 
956          * Mergedirs have the 'Merged' and 'Appliction' keywords added.
957          */
958         entry_add_implicit_keyword (new_entry, merged);
959         entry_add_implicit_keyword (new_entry, application);
960
961         if (!strcmp (rel_path, entry_get_displayname (new_entry)))
962                 entry_add_implicit_keyword (new_entry, core_quark);
963         else
964                 add_keywords_from_relative_path (new_entry, rel_path);
965 }
966
967 static Entry *
968 create_mergedir_entry (ItemDir          *id,
969                        const gchar      *rel_path,
970                        GnomeVFSFileInfo *file_info)
971 {
972         Entry *new_entry;
973
974         new_entry = create_itemdir_entry (id, rel_path, file_info);
975         if (new_entry)
976                 set_mergedir_entry_keywords (new_entry, rel_path);
977
978         return new_entry;
979 }
980
981 static Entry *
982 create_entry_or_add_dir_monitor (ItemDir          *id,
983                                  const gchar      *rel_path,
984                                  GnomeVFSFileInfo *file_info)
985 {
986         VFolderMonitor *dir_monitor;
987         Entry *ret = NULL;
988         gchar *file_uri;        
989
990         if (file_info->type == GNOME_VFS_FILE_TYPE_DIRECTORY) {
991                 /* Add monitor for subdirectory of this MergeDir/ItemDir */
992                 file_uri = vfolder_build_uri (id->uri, rel_path, NULL);
993                 dir_monitor = vfolder_monitor_dir_new (file_uri, 
994                                                        itemdir_monitor_cb, 
995                                                        id);
996                 if (dir_monitor)
997                         id->monitors = g_slist_prepend (id->monitors, 
998                                                         dir_monitor);
999                 g_free (file_uri);
1000         } 
1001         else {
1002                 switch (id->type) {
1003                 case MERGE_DIR:
1004                         ret = create_mergedir_entry (id, rel_path, file_info);
1005                         break;
1006                 case ITEM_DIR:
1007                         ret = create_itemdir_entry (id, rel_path, file_info);
1008                         break;
1009                 }
1010         }
1011
1012         return ret;
1013 }
1014
1015 static gboolean
1016 create_entry_directory_visit_cb (const gchar      *rel_path,
1017                                  GnomeVFSFileInfo *file_info,
1018                                  gboolean          recursing_will_loop,
1019                                  gpointer          user_data,
1020                                  gboolean         *recurse)
1021 {
1022         ItemDir *id = user_data;
1023
1024         create_entry_or_add_dir_monitor (id, rel_path, file_info);
1025
1026         *recurse = !recursing_will_loop;
1027         return TRUE;
1028 }
1029
1030 static gboolean
1031 vfolder_info_read_info (VFolderInfo     *info,
1032                         GnomeVFSResult  *result,
1033                         GnomeVFSContext *context)
1034 {
1035         gboolean ret = FALSE;
1036         GSList *iter;
1037
1038         if (!info->filename)
1039                 return FALSE;
1040
1041         /* Don't let set_dirty write out the file */
1042         info->loading = TRUE;
1043
1044         ret = read_vfolder_from_file (info, 
1045                                       info->filename, 
1046                                       TRUE,
1047                                       result, 
1048                                       context);
1049         if (ret) {
1050                 if (info->write_dir)
1051                         info->write_dir_monitor = 
1052                                 vfolder_monitor_dir_new (info->write_dir,
1053                                                          writedir_monitor_cb,
1054                                                          info);
1055
1056                 if (info->desktop_dir)
1057                         info->desktop_dir_monitor = 
1058                                 vfolder_monitor_dir_new (info->desktop_dir,
1059                                                          desktopdir_monitor_cb,
1060                                                          info);
1061
1062                 /* Load ItemDir/MergeDirs in order of appearance. */
1063                 for (iter = info->item_dirs; iter; iter = iter->next) {
1064                         ItemDir *id = iter->data;
1065                         VFolderMonitor *dir_monitor;
1066
1067                         /* Add a monitor for the root directory */
1068                         dir_monitor = 
1069                                 vfolder_monitor_dir_new (id->uri, 
1070                                                          itemdir_monitor_cb, 
1071                                                          id);
1072                         if (dir_monitor)
1073                                 id->monitors = g_slist_prepend (id->monitors, 
1074                                                                 dir_monitor);
1075
1076                         gnome_vfs_directory_visit (
1077                                 id->uri,
1078                                 GNOME_VFS_FILE_INFO_DEFAULT,
1079                                 GNOME_VFS_DIRECTORY_VISIT_DEFAULT,
1080                                 create_entry_directory_visit_cb,
1081                                 id);
1082                 }
1083         }
1084
1085         /* Allow set_dirty to write config file again */
1086         info->loading = FALSE;
1087
1088         return ret;
1089 }                    
1090
1091 static void
1092 vfolder_info_reset (VFolderInfo *info)
1093 {
1094         GSList *iter;
1095
1096         info->loading = TRUE;
1097         
1098         if (info->filename_monitor) {
1099                 vfolder_monitor_cancel (info->filename_monitor);
1100                 info->filename_monitor = NULL;
1101         }
1102
1103         if (info->write_dir_monitor) {
1104                 vfolder_monitor_cancel (info->write_dir_monitor);
1105                 info->write_dir_monitor = NULL;
1106         }
1107
1108         for (iter = info->item_dirs; iter; iter = iter->next) {
1109                 ItemDir *dir = iter->data;
1110                 itemdir_free (dir);
1111         }
1112         g_slist_free (info->item_dirs);
1113         info->item_dirs = NULL;
1114
1115         g_free (info->filename);
1116         g_free (info->write_dir);
1117         g_free (info->desktop_dir);
1118
1119         info->filename = NULL;
1120         info->desktop_dir = NULL;
1121         info->write_dir = NULL;
1122
1123         folder_unref (info->root);
1124         info->root = NULL;
1125
1126         g_slist_foreach (info->entries, (GFunc) entry_unref, NULL);
1127         g_slist_free (info->entries);
1128         info->entries = NULL;
1129
1130         if (info->entries_ht) {
1131                 g_hash_table_destroy (info->entries_ht);
1132                 info->entries_ht = NULL;
1133         }
1134
1135         /* Clear flags */
1136         info->read_only =
1137                 info->dirty = 
1138                 info->loading =
1139                 info->has_unallocated_folder = FALSE;
1140 }
1141
1142 \f
1143 /* 
1144  * VFolder ItemDir/MergeDir/WriteDir/DesktopDir directory monitoring
1145  */
1146 static void
1147 integrate_entry (Folder *folder, Entry *entry, gboolean do_add)
1148 {
1149         const GSList *subs;
1150         Entry *existing;
1151         Query *query;
1152         gboolean matches = FALSE;
1153
1154         for (subs = folder_list_subfolders (folder); subs; subs = subs->next) {
1155                 Folder *asub = subs->data;
1156                 integrate_entry (asub, entry, do_add);
1157         }
1158
1159         if (folder->only_unallocated)
1160                 return;
1161
1162         query = folder_get_query (folder);
1163         if (query)
1164                 matches = query_try_match (query, folder, entry);
1165
1166         existing = folder_get_entry (folder, entry_get_displayname (entry));
1167         if (existing) {
1168                 /* 
1169                  * Do nothing if the existing entry has a higher weight than the
1170                  * one we wish to add.
1171                  */
1172                 if (entry_get_weight (existing) > entry_get_weight (entry))
1173                         return;
1174                 
1175                 folder_remove_entry (folder, existing);
1176
1177                 if (do_add && matches) {
1178                         folder_add_entry (folder, entry);
1179
1180                         folder_emit_changed (folder, 
1181                                              entry_get_displayname (entry),
1182                                              GNOME_VFS_MONITOR_EVENT_CHANGED);
1183                 } else 
1184                         folder_emit_changed (folder, 
1185                                              entry_get_displayname (entry),
1186                                              GNOME_VFS_MONITOR_EVENT_DELETED);
1187         } 
1188         else if (do_add && matches) {
1189                 folder_add_entry (folder, entry);
1190
1191                 folder_emit_changed (folder, 
1192                                      entry_get_displayname (entry),
1193                                      GNOME_VFS_MONITOR_EVENT_CREATED);
1194         }
1195 }
1196
1197 static void
1198 integrate_itemdir_entry_createupdate (ItemDir                  *id,
1199                                       GnomeVFSURI              *full_uri,
1200                                       const gchar              *full_uristr,
1201                                       const gchar              *displayname,
1202                                       GnomeVFSMonitorEventType  event_type)
1203 {
1204         Entry *entry;
1205         GnomeVFSURI *real_uri;
1206         const gchar *rel_path;
1207
1208         rel_path  = strstr (full_uristr, id->uri);
1209         g_assert (rel_path != NULL);
1210         rel_path += strlen (id->uri);
1211
1212         /* Look for an existing entry with the same displayname */
1213         entry = vfolder_info_lookup_entry (id->info, displayname);
1214         if (entry) {
1215                 real_uri = entry_get_real_uri (entry);
1216
1217                 if (gnome_vfs_uri_equal (full_uri, real_uri)) {
1218                         /* Refresh */
1219                         entry_set_dirty (entry);
1220                 } 
1221                 else if (entry_get_weight (entry) < id->weight) {
1222                         /* 
1223                          * Existing entry is less important than the new
1224                          * one, so replace.
1225                          */
1226                         entry_set_filename (entry, full_uristr);
1227                         entry_set_weight (entry, id->weight);
1228                         
1229                         if (id->type == MERGE_DIR) {
1230                                 /* Add keywords from relative path */
1231                                 set_mergedir_entry_keywords (entry, rel_path);
1232                         }
1233                 }
1234
1235                 gnome_vfs_uri_unref (real_uri);
1236         } 
1237         else if (event_type == GNOME_VFS_MONITOR_EVENT_CREATED) {
1238                 GnomeVFSFileInfo *file_info;
1239                 GnomeVFSResult result;
1240
1241                 file_info = gnome_vfs_file_info_new ();
1242
1243                 result = 
1244                         gnome_vfs_get_file_info_uri (
1245                                 full_uri,
1246                                 file_info,
1247                                 GNOME_VFS_FILE_INFO_DEFAULT);
1248
1249                 if (result == GNOME_VFS_OK)
1250                         entry = create_entry_or_add_dir_monitor (id,
1251                                                                  rel_path,
1252                                                                  file_info);
1253                 
1254                 gnome_vfs_file_info_unref (file_info);
1255         }
1256
1257         if (entry) {
1258                 entry_ref (entry);
1259                 integrate_entry (id->info->root, 
1260                                  entry, 
1261                                  TRUE /* do_add */);
1262                 entry_unref (entry);
1263
1264                 id->info->modification_time = time (NULL);
1265         }
1266 }
1267
1268 static gboolean
1269 find_replacement_for_delete (ItemDir *id, Entry *entry)
1270 {
1271         GSList *iter, *miter;
1272         gint idx;
1273         
1274         idx = g_slist_index (id->info->item_dirs, id);
1275         if (idx < 0)
1276                 return FALSE;
1277
1278         iter = g_slist_nth (id->info->item_dirs, idx + 1);
1279
1280         for (; iter; iter = iter->next) {
1281                 ItemDir *id_next = iter->data;
1282
1283                 for (miter = id_next->monitors; miter; miter = miter->next) {
1284                         VFolderMonitor *monitor = miter->data;
1285                         GnomeVFSURI *check_uri;
1286                         gchar *uristr, *rel_path;
1287                         gboolean exists;
1288
1289                         uristr = 
1290                                 vfolder_build_uri (
1291                                         monitor->uri,
1292                                         entry_get_displayname (entry),
1293                                         NULL);
1294
1295                         check_uri = gnome_vfs_uri_new (uristr);
1296                         exists = gnome_vfs_uri_exists (check_uri);
1297                         gnome_vfs_uri_unref (check_uri);
1298
1299                         if (!exists) {
1300                                 g_free (uristr);
1301                                 continue;
1302                         }
1303
1304                         entry_set_filename (entry, uristr);
1305                         entry_set_weight (entry, id_next->weight);
1306
1307                         if (id_next->type == MERGE_DIR) {
1308                                 rel_path  = strstr (uristr, id_next->uri);
1309                                 rel_path += strlen (id_next->uri);
1310
1311                                 /* Add keywords based on relative path */
1312                                 set_mergedir_entry_keywords (entry, rel_path);
1313                         }
1314
1315                         g_free (uristr);
1316                         return TRUE;
1317                 }
1318         }
1319
1320         return FALSE;
1321 }
1322
1323 static void
1324 integrate_itemdir_entry_delete (ItemDir                  *id,
1325                                 GnomeVFSURI              *full_uri,
1326                                 const gchar              *displayname)
1327 {
1328         Entry *entry;
1329         GnomeVFSURI *real_uri;
1330         gboolean replaced, equal;
1331
1332         entry = vfolder_info_lookup_entry (id->info, displayname);
1333         if (!entry)
1334                 return;
1335
1336         real_uri = entry_get_real_uri (entry);
1337         equal = gnome_vfs_uri_equal (full_uri, real_uri);
1338         gnome_vfs_uri_unref (real_uri);
1339
1340         /* Only care if its the currently visible entry being deleted */
1341         if (!equal)
1342                 return;
1343
1344         replaced = find_replacement_for_delete (id, entry);
1345
1346         entry_ref (entry);
1347         integrate_entry (id->info->root, entry, replaced /* do_add */);
1348         entry_unref (entry);
1349
1350         id->info->modification_time = time (NULL);
1351 }
1352
1353 static void
1354 itemdir_monitor_cb (GnomeVFSMonitorHandle    *handle,
1355                     const gchar              *monitor_uri,
1356                     const gchar              *info_uri,
1357                     GnomeVFSMonitorEventType  event_type,
1358                     gpointer                  user_data)
1359 {
1360         ItemDir *id = user_data;
1361         gchar *filename;
1362         GnomeVFSURI *uri;
1363
1364         D (g_print ("*** Itemdir '%s' monitor %s%s%s called! ***\n", 
1365                     info_uri,
1366                     event_type == GNOME_VFS_MONITOR_EVENT_CREATED ? "CREATED":"",
1367                     event_type == GNOME_VFS_MONITOR_EVENT_DELETED ? "DELETED":"",
1368                     event_type == GNOME_VFS_MONITOR_EVENT_CHANGED ? "CHANGED":""));
1369
1370         /* Operating on the whole directory, ignore */
1371         if (!strcmp (monitor_uri, info_uri) ||
1372             !vfolder_check_extension (info_uri, ".desktop"))
1373                 return;
1374
1375         uri = gnome_vfs_uri_new (info_uri);
1376         filename = gnome_vfs_uri_extract_short_name (uri);
1377
1378         switch (event_type) {
1379         case GNOME_VFS_MONITOR_EVENT_CREATED:
1380         case GNOME_VFS_MONITOR_EVENT_CHANGED:
1381                 VFOLDER_INFO_WRITE_LOCK (id->info);
1382                 integrate_itemdir_entry_createupdate (id,
1383                                                       uri,
1384                                                       info_uri,
1385                                                       filename,
1386                                                       event_type);
1387                 VFOLDER_INFO_WRITE_UNLOCK (id->info);
1388                 break;
1389         case GNOME_VFS_MONITOR_EVENT_DELETED:
1390                 VFOLDER_INFO_WRITE_LOCK (id->info);
1391                 integrate_itemdir_entry_delete (id, uri, filename);
1392                 VFOLDER_INFO_WRITE_UNLOCK (id->info);
1393                 break;
1394         default:
1395                 break;
1396         }
1397
1398         gnome_vfs_uri_unref (uri);
1399         g_free (filename);
1400 }
1401
1402 static void
1403 integrate_writedir_entry_changed (Folder      *folder, 
1404                                   gchar       *displayname,
1405                                   GnomeVFSURI *changed_uri)
1406 {
1407         Entry *entry;
1408         GnomeVFSURI *real_uri;
1409         const GSList *subs;
1410
1411         entry = folder_get_entry (folder, displayname);
1412         if (entry) {
1413                 real_uri = entry_get_real_uri (entry);
1414
1415                 if (gnome_vfs_uri_equal (real_uri, changed_uri)) {
1416                         entry_set_dirty (entry);
1417                         folder_emit_changed (folder, 
1418                                              displayname,
1419                                              GNOME_VFS_MONITOR_EVENT_CHANGED);
1420                 }
1421
1422                 gnome_vfs_uri_unref (real_uri);
1423         }
1424
1425         for (subs = folder_list_subfolders (folder); subs; subs = subs->next) {
1426                 Folder *asub = subs->data;
1427                 integrate_writedir_entry_changed (asub, 
1428                                                   displayname, 
1429                                                   changed_uri);
1430         }
1431 }
1432
1433 static void 
1434 writedir_monitor_cb (GnomeVFSMonitorHandle    *handle,
1435                      const gchar              *monitor_uri,
1436                      const gchar              *info_uri,
1437                      GnomeVFSMonitorEventType  event_type,
1438                      gpointer                  user_data)
1439 {
1440         VFolderInfo *info = user_data;
1441         GnomeVFSURI *uri;
1442         gchar *filename, *filename_ts;
1443
1444         /* Operating on the whole directory, ignore */
1445         if (!strcmp (monitor_uri, info_uri) ||
1446             (!vfolder_check_extension (info_uri, ".desktop") && 
1447              !vfolder_check_extension (info_uri, ".directory")))
1448                 return;
1449
1450         switch (event_type) {
1451         case GNOME_VFS_MONITOR_EVENT_CHANGED:
1452                 uri = gnome_vfs_uri_new (info_uri);
1453                 filename_ts = gnome_vfs_uri_extract_short_name (uri);
1454                 filename = vfolder_untimestamp_file_name (filename_ts);
1455
1456                 VFOLDER_INFO_WRITE_LOCK (info);
1457                 integrate_writedir_entry_changed (info->root, filename, uri);
1458                 VFOLDER_INFO_WRITE_UNLOCK (info);
1459
1460                 gnome_vfs_uri_unref (uri);
1461                 g_free (filename_ts);
1462                 g_free (filename);
1463                 break;
1464         case GNOME_VFS_MONITOR_EVENT_DELETED:
1465         case GNOME_VFS_MONITOR_EVENT_CREATED:
1466         default:
1467                 break;
1468         }
1469 }
1470
1471 static void 
1472 desktopdir_monitor_cb (GnomeVFSMonitorHandle    *handle,
1473                        const gchar              *monitor_uri,
1474                        const gchar              *info_uri,
1475                        GnomeVFSMonitorEventType  event_type,
1476                        gpointer                  user_data)
1477 {
1478         VFolderInfo *info = user_data;
1479         GnomeVFSURI *uri;
1480
1481         /* Operating on the whole directory, ignore */
1482         if (!strcmp (monitor_uri, info_uri) ||
1483             !vfolder_check_extension (info_uri, ".directory"))
1484                 return;
1485
1486         switch (event_type) {
1487         case GNOME_VFS_MONITOR_EVENT_CHANGED:
1488                 uri = gnome_vfs_uri_new (info_uri);
1489
1490                 VFOLDER_INFO_WRITE_LOCK (info);
1491                 integrate_writedir_entry_changed (info->root, 
1492                                                   ".directory", 
1493                                                   uri);
1494                 VFOLDER_INFO_WRITE_UNLOCK (info);
1495
1496                 gnome_vfs_uri_unref (uri);
1497                 break;
1498         case GNOME_VFS_MONITOR_EVENT_DELETED:
1499         case GNOME_VFS_MONITOR_EVENT_CREATED:
1500         default:
1501                 break;
1502         }
1503 }
1504
1505 \f
1506 /* 
1507  * .vfolder-info monitoring
1508  */
1509 static void
1510 check_monitors_foreach (gpointer key, gpointer val, gpointer user_data)
1511 {
1512         MonitorHandle *handle = key;
1513         GSList *children = val;
1514         GnomeVFSURI *uri, *curi;
1515         const gchar *path;
1516
1517         uri = handle->uri;
1518         path = gnome_vfs_uri_get_path (handle->uri);
1519
1520         if (handle->type == GNOME_VFS_MONITOR_DIRECTORY) {
1521                 Folder *folder;
1522                 GSList *new_children, *iter, *found;
1523
1524                 folder = vfolder_info_get_folder (handle->info, path);
1525                 if (!folder) {
1526                         gnome_vfs_monitor_callback (
1527                                 (GnomeVFSMethodHandle *) handle,
1528                                 handle->uri,
1529                                 GNOME_VFS_MONITOR_EVENT_DELETED);
1530                         return;
1531                 }
1532
1533                 /* 
1534                  * FIXME: If someone has an <OnlyUnallocated> folder which also
1535                  *        has a <Query>, we won't receive change events for
1536                  *        children matching the query... I think this is corner
1537                  *        enough to ignore * though.  
1538                  */
1539                 if (folder->only_unallocated)
1540                         return;
1541
1542                 new_children = folder_list_children (folder);
1543
1544                 for (iter = children; iter; iter = iter->next) {
1545                         gchar *child_name = iter->data;
1546
1547                         /* Look for a child with the same name */
1548                         found = g_slist_find_custom (new_children,
1549                                                      child_name,
1550                                                      (GCompareFunc) strcmp);
1551                         if (found) {
1552                                 g_free (found->data);
1553                                 new_children = 
1554                                         g_slist_delete_link (new_children, 
1555                                                              found);
1556                         } else {
1557                                 curi = 
1558                                         gnome_vfs_uri_append_file_name (
1559                                                 handle->uri, 
1560                                                 child_name);
1561
1562                                 gnome_vfs_monitor_callback (
1563                                         (GnomeVFSMethodHandle *) handle,
1564                                         curi,
1565                                         GNOME_VFS_MONITOR_EVENT_DELETED);
1566
1567                                 gnome_vfs_uri_unref (curi);
1568                         }
1569
1570                         g_free (child_name);
1571                 }
1572
1573                 /* Whatever is left is new, send created events */
1574                 for (iter = new_children; iter; iter = iter->next) {
1575                         gchar *child_name = iter->data;
1576
1577                         curi = gnome_vfs_uri_append_file_name (handle->uri, 
1578                                                                child_name);
1579
1580                         gnome_vfs_monitor_callback (
1581                                 (GnomeVFSMethodHandle *) handle,
1582                                 curi,
1583                                 GNOME_VFS_MONITOR_EVENT_CREATED);
1584
1585                         gnome_vfs_uri_unref (curi);
1586                         g_free (child_name);
1587                 }
1588
1589                 g_slist_free (new_children);
1590                 g_slist_free (children);
1591         } 
1592         else {
1593                 gboolean found;
1594
1595                 found = vfolder_info_get_entry (handle->info, path) ||
1596                         vfolder_info_get_folder (handle->info, path);
1597
1598                 gnome_vfs_monitor_callback (
1599                         (GnomeVFSMethodHandle *) handle,
1600                         handle->uri,
1601                         found ?
1602                                 GNOME_VFS_MONITOR_EVENT_CHANGED :
1603                                 GNOME_VFS_MONITOR_EVENT_DELETED);
1604         }
1605 }
1606
1607 static gboolean vfolder_info_init (VFolderInfo *info);
1608
1609 static gboolean
1610 filename_monitor_handle (gpointer user_data)
1611 {
1612         VFolderInfo *info = user_data;
1613         GHashTable *monitors;
1614         GSList *iter;
1615
1616         D (g_print ("*** PROCESSING .vfolder-info!!! ***\n"));
1617         
1618         monitors = g_hash_table_new (g_direct_hash, g_direct_equal);
1619
1620         VFOLDER_INFO_WRITE_LOCK (info);
1621
1622         /* Don't emit any events while we load */
1623         info->loading = TRUE;
1624
1625         /* Compose a hash of all existing monitors and their children */
1626         for (iter = info->requested_monitors; iter; iter = iter->next) {
1627                 MonitorHandle *mhandle = iter->data;
1628                 GSList *monitored_paths = NULL;
1629                 Folder *folder;
1630
1631                 if (mhandle->type == GNOME_VFS_MONITOR_DIRECTORY) {
1632                         folder = 
1633                                 vfolder_info_get_folder (
1634                                         info, 
1635                                         gnome_vfs_uri_get_path (mhandle->uri));
1636                         if (folder)
1637                                 monitored_paths = folder_list_children (folder);
1638                 }
1639
1640                 g_hash_table_insert (monitors, mhandle, monitored_paths);
1641         }
1642
1643         vfolder_info_reset (info);
1644         vfolder_info_init (info);
1645
1646         /* Start sending events again */
1647         info->loading = FALSE;
1648
1649         /* Traverse monitor hash and diff with newly read folder structure */
1650         g_hash_table_foreach (monitors, check_monitors_foreach, info);
1651
1652         VFOLDER_INFO_WRITE_UNLOCK (info);
1653
1654         g_hash_table_destroy (monitors);
1655
1656         info->filename_reload_tag = 0;
1657         return FALSE;
1658 }
1659
1660 static void
1661 filename_monitor_cb (GnomeVFSMonitorHandle *handle,
1662                      const gchar *monitor_uri,
1663                      const gchar *info_uri,
1664                      GnomeVFSMonitorEventType event_type,
1665                      gpointer user_data)
1666 {
1667         VFolderInfo *info = user_data;
1668
1669         D (g_print ("*** Filename '%s' monitor %s%s%s called! ***\n",
1670                     info_uri,
1671                     event_type == GNOME_VFS_MONITOR_EVENT_CREATED ? "CREATED":"",
1672                     event_type == GNOME_VFS_MONITOR_EVENT_DELETED ? "DELETED":"",
1673                     event_type == GNOME_VFS_MONITOR_EVENT_CHANGED ? "CHANGED":""));
1674
1675         if (info->filename_reload_tag) {
1676                 g_source_remove (info->filename_reload_tag);
1677                 info->filename_reload_tag = 0;
1678         }
1679
1680         /* 
1681          * Don't process the .vfolder-info for 2 seconds after a delete event or
1682          * .5 seconds after a create event.  This allows files to be rewritten
1683          * before we start reading it and possibly copying the system default
1684          * file over top of it.  
1685          */
1686         switch (event_type) {
1687         case GNOME_VFS_MONITOR_EVENT_DELETED:
1688                 info->filename_reload_tag = 
1689                         g_timeout_add (2000, filename_monitor_handle, info);
1690                 break;
1691         case GNOME_VFS_MONITOR_EVENT_CREATED:
1692                 info->filename_reload_tag = 
1693                         g_timeout_add (500, filename_monitor_handle, info);
1694                 break;
1695         case GNOME_VFS_MONITOR_EVENT_CHANGED:
1696         default:
1697                 filename_monitor_handle (info);
1698                 break;
1699         }
1700 }
1701
1702 \f
1703 /* 
1704  * VFolderInfo Implementation
1705  */
1706 static VFolderInfo *
1707 vfolder_info_new (const char *scheme)
1708 {
1709         VFolderInfo *info;
1710
1711         info = g_new0 (VFolderInfo, 1);
1712         info->scheme = g_strdup (scheme);
1713
1714         g_static_rw_lock_init (&info->rw_lock);
1715
1716         return info;
1717 }
1718
1719 static void
1720 vfolder_info_find_filenames (VFolderInfo *info)
1721 {
1722         gchar *scheme = info->scheme;
1723         GnomeVFSURI *file_uri;
1724         gboolean exists;
1725
1726         /* 
1727          * FIXME: load from gconf 
1728          */
1729
1730         /* 
1731          * 1st: Try mandatory system-global file located at
1732          * /etc/gnome-vfs-2.0/vfolders/scheme.vfolder-info.  Writability will
1733          * depend on permissions of this file.
1734          */
1735         info->filename = g_strconcat (SYSCONFDIR,
1736                                       "/gnome-vfs-2.0/vfolders/",
1737                                       scheme, ".vfolder-info",
1738                                       NULL);
1739         file_uri = gnome_vfs_uri_new (info->filename);
1740
1741         exists = gnome_vfs_uri_exists (file_uri);
1742         gnome_vfs_uri_unref (file_uri);
1743
1744         if (!exists) {
1745                 /* 
1746                  * 2nd: Try user-private ~/.gnome2/vfolders/scheme.vfolder-info 
1747                  */
1748                 g_free (info->filename);
1749                 info->filename = g_strconcat (g_get_home_dir (),
1750                                               "/" DOT_GNOME "/vfolders/",
1751                                               scheme, ".vfolder-info",
1752                                               NULL);
1753         }
1754
1755         /* 
1756          * Special case for applications-all-users where we want to add any
1757          * paths specified in $GNOME2_PATH, for people installing in strange
1758          * places.
1759          */
1760         if (strcmp (scheme, "applications-all-users")) {
1761                 int i;
1762                 const char *path;
1763                 char *dir, **ppath;
1764                 ItemDir *id;
1765                 int weight = 800;
1766
1767                 path = g_getenv ("GNOME2_PATH");
1768                 if (path) {
1769                         ppath = g_strsplit (path, ":", -1);
1770
1771                         for (i = 0; ppath[i] != NULL; i++) {
1772                                 dir = g_build_filename (ppath[i], 
1773                                                         "/share/applications/",
1774                                                         NULL);
1775                                 id = itemdir_new (info, 
1776                                                   dir, 
1777                                                   ITEM_DIR,
1778                                                   weight--);
1779                                 g_free (dir);
1780                         }
1781
1782                         g_strfreev (ppath);
1783                 }
1784         }
1785 }
1786
1787 static gboolean
1788 g_str_case_equal (gconstpointer v1,
1789                   gconstpointer v2)
1790 {
1791         const gchar *string1 = v1;
1792         const gchar *string2 = v2;
1793   
1794         return g_ascii_strcasecmp (string1, string2) == 0;
1795 }
1796
1797 /* 31 bit hash function */
1798 static guint
1799 g_str_case_hash (gconstpointer key)
1800 {
1801         const char *p = key;
1802         guint h = g_ascii_toupper (*p);
1803         
1804         if (h)
1805                 for (p += 1; *p != '\0'; p++)
1806                         h = (h << 5) - h + g_ascii_toupper (*p);
1807
1808         return h;
1809 }
1810
1811 static gboolean
1812 vfolder_info_init (VFolderInfo *info)
1813 {
1814         gchar *all_user_scheme;
1815
1816         info->loading = TRUE;
1817         info->entries_ht = g_hash_table_new (g_str_case_hash, g_str_case_equal);
1818         info->root = folder_new (info, "Root", TRUE);
1819
1820         /* 
1821          * Set the extend uri for the root folder to the -all-users version of
1822          * the scheme, in case the user doesn't have a private .vfolder-info
1823          * file yet.  
1824          */
1825         all_user_scheme = g_strconcat (info->scheme, "-all-users:///", NULL);
1826         folder_set_extend_uri (info->root, all_user_scheme);
1827         g_free (all_user_scheme);
1828
1829         /* 
1830          * Set the default writedir, in case there is no .vfolder-info for this
1831          * scheme yet.  Otherwise this will be overwritten when we read our
1832          * source.
1833          */
1834         info->write_dir = g_strconcat (g_get_home_dir (),
1835                                        "/" DOT_GNOME "/vfolders/",
1836                                        info->scheme,
1837                                        NULL);
1838
1839         /* Figure out which .vfolder-info to read */
1840         vfolder_info_find_filenames (info);
1841
1842         if (g_getenv ("GNOME_VFS_VFOLDER_INFODIR")) {
1843                 gchar *filename = g_strconcat (info->scheme, 
1844                                                ".vfolder-info", 
1845                                                NULL);
1846
1847                 g_free (info->filename);
1848                 info->filename = 
1849                         vfolder_build_uri (
1850                                 g_getenv ("GNOME_VFS_VFOLDER_INFODIR"),
1851                                 filename,
1852                                 NULL);
1853                 g_free (filename);
1854         }
1855
1856         if (g_getenv ("GNOME_VFS_VFOLDER_WRITEDIR")) {
1857                 g_free (info->write_dir);
1858                 info->write_dir = 
1859                         vfolder_build_uri (
1860                                 g_getenv ("GNOME_VFS_VFOLDER_WRITEDIR"),
1861                                 info->scheme,
1862                                 NULL);
1863         }
1864
1865         info->filename_monitor = 
1866                 vfolder_monitor_file_new (info->filename,
1867                                           filename_monitor_cb,
1868                                           info);
1869
1870         info->modification_time = time (NULL);
1871         info->loading = FALSE;
1872         info->dirty = FALSE;
1873
1874         /* Read from the user's .vfolder-info if it exists */
1875         return vfolder_info_read_info (info, NULL, NULL);
1876 }
1877
1878 static void
1879 vfolder_info_destroy (VFolderInfo *info)
1880 {
1881         if (info == NULL)
1882                 return;
1883
1884         vfolder_info_reset (info);
1885
1886         if (info->filename_reload_tag)
1887                 g_source_remove (info->filename_reload_tag);
1888
1889         g_static_rw_lock_free (&info->rw_lock);
1890
1891         g_free (info->scheme);
1892
1893         while (info->requested_monitors) {
1894                 GnomeVFSMethodHandle *monitor = info->requested_monitors->data;
1895                 vfolder_info_cancel_monitor (monitor);
1896         }
1897
1898         g_free (info);
1899 }
1900
1901 /* 
1902  * Call to recursively list folder contents, causing them to allocate entries,
1903  * so that we get OnlyUnallocated folder counts correctly.
1904  */
1905 static void
1906 load_folders (Folder *folder)
1907 {
1908         const GSList *iter;
1909
1910         for (iter = folder_list_subfolders (folder); iter; iter = iter->next) {
1911                 Folder *folder = iter->data;
1912                 load_folders (folder);
1913         }
1914 }
1915
1916 static GHashTable *infos = NULL;
1917 G_LOCK_DEFINE_STATIC (vfolder_lock);
1918
1919 VFolderInfo *
1920 vfolder_info_locate (const gchar *scheme)
1921 {
1922         VFolderInfo *info = NULL;
1923
1924         G_LOCK (vfolder_lock);
1925
1926         if (!infos) {
1927                 infos = 
1928                         g_hash_table_new_full (
1929                                 g_str_hash, 
1930                                 g_str_equal,
1931                                 NULL,
1932                                 (GDestroyNotify) vfolder_info_destroy);
1933         }
1934
1935         info = g_hash_table_lookup (infos, scheme);
1936         if (info) {
1937                 G_UNLOCK (vfolder_lock);
1938                 return info;
1939         }
1940         else {
1941                 info = vfolder_info_new (scheme);
1942                 g_hash_table_insert (infos, info->scheme, info);
1943
1944                 VFOLDER_INFO_WRITE_LOCK (info);
1945                 G_UNLOCK (vfolder_lock);
1946
1947                 if (!vfolder_info_init (info)) {
1948                         D (g_print ("DESTROYING INFO FOR SCHEME: %s\n", 
1949                                     scheme));
1950
1951                         G_LOCK (vfolder_lock);
1952                         g_hash_table_remove (infos, info);
1953                         G_UNLOCK (vfolder_lock);
1954
1955                         return NULL;
1956                 }
1957                         
1958                 if (info->has_unallocated_folder) {
1959                         info->loading = TRUE;
1960                         load_folders (info->root);
1961                         info->loading = FALSE;
1962                 }
1963
1964                 VFOLDER_INFO_WRITE_UNLOCK (info);
1965                 return info;
1966         }
1967 }
1968
1969 void
1970 vfolder_info_set_dirty (VFolderInfo *info)
1971 {
1972         if (info->loading)
1973                 return;
1974
1975         info->dirty = TRUE;
1976 }
1977
1978 static Folder *
1979 get_folder_for_path_list_n (Folder    *parent, 
1980                             gchar    **paths, 
1981                             gint       path_index,
1982                             gboolean   skip_last) 
1983 {
1984         Folder *child;
1985         gchar *subname, *subsubname;
1986
1987         if (!parent || folder_is_hidden (parent))
1988                 return NULL;
1989
1990         subname = paths [path_index];
1991         if (!subname)
1992                 return parent;
1993
1994         subsubname = paths [path_index + 1];
1995         if (!subsubname && skip_last)
1996                 return parent;
1997
1998         if (*subname == '\0')
1999                 child = parent;
2000         else
2001                 child = folder_get_subfolder (parent, subname);
2002
2003         return get_folder_for_path_list_n (child, 
2004                                            paths, 
2005                                            path_index + 1, 
2006                                            skip_last);
2007 }
2008
2009 static Folder *
2010 get_folder_for_path (Folder *root, const gchar *path, gboolean skip_last) 
2011 {
2012         gchar **paths;
2013         Folder *folder;
2014
2015         paths = g_strsplit (path, "/", -1);
2016         if (!paths)
2017                 return NULL;
2018
2019         folder = get_folder_for_path_list_n (root, paths, 0, skip_last);
2020
2021         g_strfreev (paths);
2022         
2023         return folder;
2024 }
2025
2026 Folder *
2027 vfolder_info_get_parent (VFolderInfo *info, const gchar *path)
2028 {
2029         return get_folder_for_path (info->root, path, TRUE);
2030 }
2031
2032 Folder *
2033 vfolder_info_get_folder (VFolderInfo *info, const gchar *path)
2034 {
2035         return get_folder_for_path (info->root, path, FALSE);
2036 }
2037
2038 Entry *
2039 vfolder_info_get_entry (VFolderInfo *info, const gchar *path)
2040 {
2041         Folder *parent;
2042         gchar *subname;
2043
2044         parent = vfolder_info_get_parent (info, path);
2045         if (!parent)
2046                 return NULL;
2047
2048         subname = strrchr (path, '/');
2049         if (!subname)
2050                 return NULL;
2051         else
2052                 subname++;
2053
2054         return folder_get_entry (parent, subname);
2055 }
2056
2057 const GSList *
2058 vfolder_info_list_all_entries (VFolderInfo *info)
2059 {
2060         return info->entries;
2061 }
2062
2063 Entry *
2064 vfolder_info_lookup_entry (VFolderInfo *info, const gchar *name)
2065 {
2066         return g_hash_table_lookup (info->entries_ht, name);
2067 }
2068
2069 void 
2070 vfolder_info_add_entry (VFolderInfo *info, Entry *entry)
2071 {
2072         info->entries = g_slist_prepend (info->entries, entry);
2073         g_hash_table_insert (info->entries_ht, 
2074                              (gchar *) entry_get_displayname (entry),
2075                              entry);
2076 }
2077
2078 void 
2079 vfolder_info_remove_entry (VFolderInfo *info, Entry *entry)
2080 {
2081         info->entries = g_slist_remove (info->entries, entry);
2082         g_hash_table_remove (info->entries_ht, 
2083                              entry_get_displayname (entry));
2084 }
2085
2086 #ifdef VFOLDER_DEBUG
2087 #define DEBUG_CHANGE_EMIT(_change_uri, _handle_uri)                         \
2088         g_print ("EMITTING CHANGE: %s for %s, %s%s%s\n",                    \
2089                  _change_uri,                                               \
2090                  _handle_uri,                                               \
2091                  event_type==GNOME_VFS_MONITOR_EVENT_CREATED?"CREATED":"",  \
2092                  event_type==GNOME_VFS_MONITOR_EVENT_DELETED?"DELETED":"",  \
2093                  event_type==GNOME_VFS_MONITOR_EVENT_CHANGED?"CHANGED":"")
2094 #else
2095 #define DEBUG_CHANGE_EMIT(_change_uri, _handle_uri)
2096 #endif
2097
2098 void 
2099 vfolder_info_emit_change (VFolderInfo              *info,
2100                           const char               *path,
2101                           GnomeVFSMonitorEventType  event_type)
2102 {
2103         GSList *iter;
2104         GnomeVFSURI *uri;
2105         gchar *escpath, *uristr;
2106
2107         if (info->loading) 
2108                 return;
2109
2110         escpath = gnome_vfs_escape_path_string (path);
2111         uristr = g_strconcat (info->scheme, "://", escpath, NULL);
2112         uri = gnome_vfs_uri_new (uristr);
2113
2114         for (iter = info->requested_monitors; iter; iter = iter->next) {
2115                 MonitorHandle *handle = iter->data;
2116
2117                 if (gnome_vfs_uri_equal (uri, handle->uri) ||
2118                     (handle->type == GNOME_VFS_MONITOR_DIRECTORY &&
2119                      gnome_vfs_uri_is_parent (handle->uri, 
2120                                               uri, 
2121                                               FALSE))) {
2122                         DEBUG_CHANGE_EMIT (uristr, handle->uri->text);
2123
2124                         gnome_vfs_monitor_callback (
2125                                 (GnomeVFSMethodHandle *) handle,
2126                                 uri,
2127                                 event_type);
2128                 }
2129         }
2130
2131         gnome_vfs_uri_unref (uri);
2132         g_free (escpath);
2133         g_free (uristr);
2134 }
2135
2136 void
2137 vfolder_info_add_monitor (VFolderInfo           *info,
2138                           GnomeVFSMonitorType    type,
2139                           GnomeVFSURI           *uri,
2140                           GnomeVFSMethodHandle **handle)
2141 {
2142         MonitorHandle *monitor = g_new0 (MonitorHandle, 1);
2143         monitor->info = info;
2144         monitor->type = type;
2145
2146         monitor->uri = uri;
2147         gnome_vfs_uri_ref (uri);
2148
2149         info->requested_monitors = g_slist_prepend (info->requested_monitors,
2150                                                     monitor);
2151
2152         D (g_print ("EXTERNALLY WATCHING: %s\n", 
2153                     gnome_vfs_uri_to_string (uri, 0)));
2154         
2155         *handle = (GnomeVFSMethodHandle *) monitor;
2156 }
2157
2158 void 
2159 vfolder_info_cancel_monitor (GnomeVFSMethodHandle  *handle)
2160 {
2161         MonitorHandle *monitor = (MonitorHandle *) handle;
2162
2163         monitor->info->requested_monitors = 
2164                 g_slist_remove (monitor->info->requested_monitors, monitor);
2165
2166         gnome_vfs_uri_unref (monitor->uri);
2167         g_free (monitor);
2168 }
2169
2170 void
2171 vfolder_info_destroy_all (void)
2172 {
2173         G_LOCK (vfolder_lock);
2174
2175         if (infos) {
2176                 g_hash_table_destroy (infos);
2177                 infos = NULL;
2178         }
2179
2180         G_UNLOCK (vfolder_lock);
2181 }
2182
2183 void
2184 vfolder_info_dump_entries (VFolderInfo *info, int offset)
2185 {
2186         g_slist_foreach (info->entries, 
2187                          (GFunc) entry_dump, 
2188                          GINT_TO_POINTER (offset));
2189 }