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