Configuration system for beagle

Designed to give the users a bit more control.

Currently this just gives them access to add more roots and ignore patterns.
Possible ideas for future configuration include enabling/disabling backends,
setting default query domain to local/global, setting default number of hits
per backend, see the wiki for other ideas.

This is quite early on, just a preview to hopefully get some comments on the
design. I keep changing my mind so any comments are appreciated :)

Here's how it works:

- The config is kept in GConf at /apps/beagle

- Beagle.Util.Conf is a new class which provides access to read and soon modify
  the current configuration.

- Beagle.Util.Conf is split up into "sections". There is currently only one
  section: indexing.

- Each section has a series of "options" which can be invoked by the user or by
  other areas of beagle code. For example, BU.Conf.Indexing has an "AddRoot"
  section option.

- On startup, the config is loaded. Each area of code which will listen for
  config options has to use BU.Conf to get an initial configuration.

- Soon, those areas of code will also subscribe to BU.Conf to be notified 
  of configuration changes. (GConf# provides a notification mechanism.)

- There is a command-line interface to Beagle.Util.Conf, called beagle-config.
  beagle-config allows execution of "section options" in Beagle.Util.Conf

- There's quite a bit left to do, see the changes made to the TODO file.

diff -urNpX dontdiff beagle-orig/beagled/BeagleDaemon.cs beagle/beagled/BeagleDaemon.cs
--- beagle-orig/beagled/BeagleDaemon.cs	2005-04-07 08:58:11.000000000 +0100
+++ beagle/beagled/BeagleDaemon.cs	2005-04-14 17:30:13.000000000 +0100
@@ -377,6 +377,9 @@ namespace Beagle.Daemon {
 				return 1;
 			}
 
+			// Load user-specified config
+			Conf.Load ();
+
 			// Set up our helper process and the associated monitoring
 			IndexHelperFu.Start ();
 			
diff -urNpX dontdiff beagle-orig/beagled/FileSystemQueryable/FileNameFilter.cs beagle/beagled/FileSystemQueryable/FileNameFilter.cs
--- beagle-orig/beagled/FileSystemQueryable/FileNameFilter.cs	2005-03-11 04:58:21.000000000 +0000
+++ beagle/beagled/FileSystemQueryable/FileNameFilter.cs	2005-04-14 17:47:30.000000000 +0100
@@ -36,12 +36,17 @@ namespace Beagle.Daemon.FileSystemQuerya
 	public class FileNameFilter {
 		
 		static string home_dir;
+		private FileSystemModel model = null;
 
 		static FileNameFilter ()
 		{
 			home_dir = PathFinder.HomeDir;
 		}
 
+		public FileNameFilter (FileSystemModel model) : this ()
+		{
+			this.model = model;
+		}
 
 		private class Pattern {
 			private string exactMatch;
@@ -96,36 +101,15 @@ namespace Beagle.Daemon.FileSystemQuerya
 			}
 		}
 
-		static ArrayList LoadPatterns (string filename)
-		{
-			if (! File.Exists (filename))
-				return null;
-			
-			ArrayList array = new ArrayList ();
-			StreamReader sr = new StreamReader (filename);
-			string line;
-			while ((line = sr.ReadLine ()) != null) {
-				line = line.Trim ();
-				if (line.Length > 0) {
-					Pattern pattern = new Pattern (line);
-					array.Add (pattern);
-				}
-			}
-			
-			return array;
-		}
-
 		//////////////////////////////////////////////////////////////////////
-		
-		private ArrayList defaultPatternsToIgnore;
+
+		private ArrayList patternsToIgnore;
 
 		private void SetupDefaultPatternsToIgnore ()
 		{
-			defaultPatternsToIgnore = new ArrayList ();
-
 			// Add our default skip patterns.
-			// FIXME: This probably shouldn't be hard-wired.  Or should it?
-			AddDefaultPatternToIgnore (".*",
+			AddPatternToIgnore (new string [] {
+						   ".*",
 						   "*~",
 						   "#*#",
 						   "*.o",
@@ -156,6 +140,7 @@ namespace Beagle.Daemon.FileSystemQuerya
 						   "Makefile.am",
 						   "Makefile.in",
 						   "CVS",
+						   "SCCS",
 						   // Garbage generated by the autotools
 						   "conftest",
 						   "confdefs.h",
@@ -163,113 +148,30 @@ namespace Beagle.Daemon.FileSystemQuerya
 						   "confstat*",
 						   "/conf[0-9]+.sh/",
 						   "/conf[0-9]+.file/"
-				);
-			
-			// Read the ~/.neverindex file, which contains patterns
-			// for files that should always be ignored.
-			string neverindex = Path.Combine (home_dir, ".neverindex");
-			ArrayList patterns = LoadPatterns (neverindex);
-			if (patterns != null) {
-				foreach (Pattern pattern in patterns)
-					defaultPatternsToIgnore.Add (pattern);
-			}
+				});
 		}
 
-		public void AddDefaultPatternToIgnore (string pattern)
+		public void AddPatternToIgnore (string pattern)
 		{
-			defaultPatternsToIgnore.Add (new Pattern (pattern));
+			patternsToIgnore.Add (new Pattern (pattern));
 		}
-		
-		public void AddDefaultPatternToIgnore (params string[] patterns)
+
+		public void AddPatternToIgnore (IEnumerable patterns)
 		{
 			foreach (string pattern in patterns)
-				AddDefaultPatternToIgnore (pattern);
+				AddPatternToIgnore (pattern);
 		}
 		
 		/////////////////////////////////////////////////////////////
 
 		public FileNameFilter ()
 		{
+			patternsToIgnore = new ArrayList ();
 			SetupDefaultPatternsToIgnore ();
 		}
 
 		/////////////////////////////////////////////////////////////
 
-		private class PerDirectoryInfo {
-			string dir;
-			DateTime noindexTimestamp;
-			DateTime noindexLastCheck;
-			ArrayList patternsToIgnore;
-
-			public PerDirectoryInfo (string _dir)
-			{
-				dir = _dir;
-				ConditionallyLoad ();
-			}
-
-			public bool IsEmpty {
-				get { return patternsToIgnore == null; }
-			}
-
-			// If the directory's .noindex file has changed, load it.
-			// FIXME: This should be done with inotify instead of using
-			// this sort of seek-happy polling.
-			private void ConditionallyLoad ()
-			{
-				// Only re-check the .noindex file every 11 seconds.
-				DateTime now = DateTime.Now;
-				if ((now - noindexLastCheck).TotalSeconds < 11)
-					return;
-				noindexLastCheck = now;
-
-				string noindex = Path.Combine (dir, ".noindex");
-
-				if (File.Exists (noindex)) {
-					DateTime timestamp = File.GetLastWriteTime (noindex);
-					if (timestamp != noindexTimestamp) {
-						patternsToIgnore = LoadPatterns (noindex);
-						noindexTimestamp = timestamp;
-					}
-				} else {
-					patternsToIgnore = null;
-				}
-			}
-
-			public bool Ignore (string name)
-			{
-				ConditionallyLoad ();
-				if (patternsToIgnore == null)
-					return false;
-				if (patternsToIgnore.Count == 0) // i.e. IgnoreAll
-					return true;
-				foreach (Pattern pattern in patternsToIgnore)
-					if (pattern.IsMatch (name))
-						return true;
-
-				return false;
-			}
-		}
-
-		
-		private Hashtable perDirectoryCache = new Hashtable ();
-
-		private PerDirectoryInfo GetPerDirectoryInfo (string dir)
-		{
-			if (dir == null)
-				return null;
-
-			// Only cache the non-trivial per-directory information
-			PerDirectoryInfo info;
-			lock (perDirectoryCache) {
-				if (! perDirectoryCache.Contains (dir)) {
-					info = new PerDirectoryInfo (dir);
-					perDirectoryCache [dir] = info.IsEmpty ? null : info;
-				}
-				info = perDirectoryCache [dir] as PerDirectoryInfo;
-			}
-			return info;
-		}
-
 		// FIXME: We could make this more efficient by storing more information.
 		// In particular, if ~/foo is an IgnoreAll directory, we could store
 		// that info in the PerDirectoryInfo of subdir ~/foo/bar so that
@@ -279,22 +181,18 @@ namespace Beagle.Daemon.FileSystemQuerya
 			if (! Path.IsPathRooted (path))
 				path = Path.GetFullPath (path);
 			
-			if (path == home_dir)
-				return false;
+			foreach (FileSystemModel.Directory root in model.Roots)
+				if (root.FullName == path)
+					return false;
 
 			string name = Path.GetFileName (path);
 
-			foreach (Pattern pattern in defaultPatternsToIgnore) {
+			foreach (Pattern pattern in patternsToIgnore)
 				if (pattern.IsMatch (name))
 					return true;
-			}
 
 			string dir = Path.GetDirectoryName (path);
-			PerDirectoryInfo perDir = GetPerDirectoryInfo (dir);
 
-			if (perDir != null && perDir.Ignore (name))
-				return true;
-			
 			// A file should be ignored if any of its parent directories
 			// is ignored.
 			return Ignore (dir);
diff -urNpX dontdiff beagle-orig/beagled/FileSystemQueryable/FileSystemModel.cs beagle/beagled/FileSystemQueryable/FileSystemModel.cs
--- beagle-orig/beagled/FileSystemQueryable/FileSystemModel.cs	2005-04-05 06:46:37.000000000 +0100
+++ beagle/beagled/FileSystemQueryable/FileSystemModel.cs	2005-04-14 17:32:37.000000000 +0100
@@ -414,7 +414,7 @@ namespace Beagle.Daemon.FileSystemQuerya
 		Hashtable path_cache = new Hashtable ();
 		Hashtable by_unique_id = new Hashtable ();
 		Queue to_be_scanned = new Queue ();
-		FileNameFilter filter = new FileNameFilter ();
+		FileNameFilter filter;
 
 		IFileEventBackend event_backend;
 		int needs_crawl_count = 0;
@@ -434,6 +434,7 @@ namespace Beagle.Daemon.FileSystemQuerya
 
 			unique_id_store = new UniqueIdStore (index_directory, index_fingerprint);
 			name_index = new NameIndex (index_directory, index_fingerprint);
+			filter = new FileNameFilter (this);
 
 			IFileAttributesStore backing_store_i;
 			backing_store_i = new FileAttributesStore_Mixed (index_directory, index_fingerprint);
@@ -461,6 +462,17 @@ namespace Beagle.Daemon.FileSystemQuerya
 			get { lock (big_lock) return roots.Clone () as ArrayList; }
 		}
 
+		public void LoadConf ()
+		{
+			filter.AddPatternToIgnore (Conf.Indexing.IgnorePatterns);
+
+			if (Conf.Indexing.IndexHomeDir)
+				AddRoot (PathFinder.HomeDir);
+
+			foreach (string root in Conf.Indexing.Roots)
+				AddRoot (root);
+		}
+
 		public Directory AddRoot (string path)
 		{
 			// Remove trailing directory separators, which cause Path.FileName
@@ -471,7 +483,9 @@ namespace Beagle.Daemon.FileSystemQuerya
 				path = path.Substring (0, path.Length-1);
 
 			Logger.Log.Debug ("Adding root {0}", path);
-				
+			
+//FIXME check that root exists (above)
+			
 			DirectoryPrivate root = new DirectoryPrivate (big_lock);
 			root.InitRoot (System.IO.Path.GetDirectoryName (path), 
 				       System.IO.Path.GetFileName (path));
@@ -487,7 +501,7 @@ namespace Beagle.Daemon.FileSystemQuerya
 				// of any existing root.
 				foreach (Directory existing_root in roots) {
 					if (existing_root.FullName == root.FullName) {
-						throw new Exception ("Attempt to re-add root " + root.FullName);
+						return null;
 						// FIXME: Is there a case where we would
 						// want to just return existing_root?
 					}
diff -urNpX dontdiff beagle-orig/beagled/FileSystemQueryable/FileSystemQueryable.cs beagle/beagled/FileSystemQueryable/FileSystemQueryable.cs
--- beagle-orig/beagled/FileSystemQueryable/FileSystemQueryable.cs	2005-04-05 06:46:37.000000000 +0100
+++ beagle/beagled/FileSystemQueryable/FileSystemQueryable.cs	2005-04-07 18:59:19.000000000 +0100
@@ -270,8 +270,7 @@ namespace Beagle.Daemon.FileSystemQuerya
 		{
 			event_backend.Start (this);
 
-			// FIXME: Shouldn't be hard-wired
-			model.AddRoot (PathFinder.HomeDir);
+			model.LoadConf ();
 
 			log.Info ("FileSystemQueryable start-up thread finished");
 			
diff -urNpX dontdiff beagle-orig/doc/TODO beagle/doc/TODO
--- beagle-orig/doc/TODO	2004-10-01 22:04:44.000000000 +0100
+++ beagle/doc/TODO	2005-04-14 17:36:28.000000000 +0100
@@ -55,3 +55,18 @@ Mail
   text/plain parts of a mail; at the very least it could index the
   HTML parts of a mail message.
 
+
+Configuration
+=============
+
+* Be tolerant of bad roots
+* Be tolerant of roots inside other roots
+* Handle already-added roots
+* Don't add the same ignore pattern twice
+* Subscriptions so that you are informed when a key changes
+* Code annotation.
+* GUI util to manage configuration. (fredrik?)
+* Writing config settings
+* Threading issues? Use GConfThreadHelper?
+* Encapsulating command calls to make the individual config options nicer
+
diff -urNpX dontdiff beagle-orig/tools/beagle-config.1 beagle/tools/beagle-config.1
--- beagle-orig/tools/beagle-config.1	1970-01-01 01:00:00.000000000 +0100
+++ beagle/tools/beagle-config.1	2005-04-14 17:57:50.000000000 +0100
@@ -0,0 +1,53 @@
+.\" beagle-config(1) manpage
+.\"
+.\" Copyright (C) 2005 Novell, Inc.
+.\"
+.TH BEAGLE-CONFIG "1" "Apr 2005" "beagle" "Linux User's Manual"
+.SH NAME
+beagle-config \- command-line interface to the Beagle configuration file
+.SH SYNOPSIS
+.B beagle-config
+\fIoptions\fR
+.br
+.B beagle-config
+\fIsections\fR
+.br
+.B beagle-config
+\fIsections\fR \fIsectionoptions\fR [\fIparams\fR]
+.SH DESCRIPTION
+.BR beagle-config
+is a command line tool designed to manipulate the Beagle configuration.
+.PP
+The configuration is stored in the \fBGConf\fR registry. It is split up into \fIsection\fR nodes, all underneath the main node \fB/apps/beagle\fR.
+.PP
+Each \fIsection\fR configures a different area of the Beagle system. Each section has a number of preset \fIsectionoptions\fR (commands) which you can perform on it via this command-line application.
+.PP
+The available sections, and available options for each section, are discovered at runtime. This manual does not attempt to document the sections in detail. This manual demonstrates how you can retrieve a list of available sections and their options.
+.PP
+\fIbeagle-config\fR can be used "offline", it does not require a Beagle daemon to be running for most operations. If a Beagle daemon is running, it will immediately be notified of any configuration changes, and your new settings will come into affect almost immediately.
+.SH OPTIONS
+.TP
+.B --help 
+Print a brief copyright and usage message.
+.TP
+.B --beagled-reload-config
+Asks the beagle daemon to reload the configuration file.
+.TP
+.B --list-sections
+Lists all of the available sections for use as the \fIsections\fR parameter.
+.SH SECTION OPTIONS
+Each section has its own unique set of options available. To see a list of supported options for a particular section, run \fBbeagle-config sectionname\fR with no additional parameters.
+.SH PARAMS
+Each option may take a number of parameters (or none). Please refer to online documentation if you require explanations of every available command.
+.SH AUTHOR
+Originally written by Daniel Drake <dsd@gentoo.org>.
+.SH "REPORTING BUGS"
+Report bugs to <dashboard-hackers@gnome.org>.
+.SH COPYRIGHT
+Copyright \(co 2005 Novell, Inc.
+.sp
+This is free software; see the source for copying conditions.  There is NO
+warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+.SH "SEE ALSO"
+.BR beagled (1),
+.BR best (1)
diff -urNpX dontdiff beagle-orig/tools/Config.cs beagle/tools/Config.cs
--- beagle-orig/tools/Config.cs	1970-01-01 01:00:00.000000000 +0100
+++ beagle/tools/Config.cs	2005-04-14 22:48:28.000000000 +0100
@@ -0,0 +1,172 @@
+//
+// Config.cs
+//
+// Copyright (C) 2005 Novell, Inc.
+//
+
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+using System;
+using System.Collections;
+using System.Collections.Specialized;
+using System.Reflection;
+
+using Beagle;
+using Beagle.Util;
+
+using Gtk;
+
+public static class ConfigTool {
+
+	private static void PrintUsageAndExit ()
+	{
+		string usage =
+			"beagle-config: Command-line interface to the Beagle config file.\n" +
+			"Web page: http://www.gnome.org/projects/beagle\n" +
+			"Copyright (C) 2005 Novell, Inc.\n\n";
+		usage +=
+			"Usage: beagle-config [OPTIONS]\n" +
+			"   or: beagle-config <SECTION>\n" +
+			"   or: beagle-config <SECTION> <SECTIONOPTION> [PARAMS]\n\n" +
+			"Options:\n" +
+			"  --beagled-reload-config\tAsk the beagle daemon to reload\n" +
+			"                         \tthe configuration file.\n" +
+			"  --list-sections\t\tList all available configuration sections.\n" +
+			"  --help\t\t\tPrint this usage message.\n\n" +
+			"If a section is specified with no options, then a list of the available commands for that section is shown.\n";
+
+		Console.WriteLine (usage);
+
+		System.Environment.Exit (0);
+	}
+
+	private static void ListSectionsAndExit ()
+	{
+		Console.WriteLine ("Available configuration sections: ");
+		foreach (string key in Conf.Sections.Keys)
+			Console.WriteLine (" - {0}", key);
+
+		System.Environment.Exit (0);
+	}
+	
+	private static void ListSectionOptionsAndExit (string sectionname, Hashtable options)
+	{
+		Console.WriteLine ("Available options for section '{0}':", sectionname);
+		foreach (string key in options.Keys) {
+			Console.Write (" - {0}", key);
+			if (options [key] != null)
+				Console.Write (" ({0})", options [key]);
+
+			Console.WriteLine ();
+		}
+		
+		System.Environment.Exit (0);
+	}
+		
+	public static void Main (string [] args)
+	{
+		Application.InitCheck ("beagle-config", ref args);
+
+		if (args.Length == 0)
+			PrintUsageAndExit ();
+
+		int i = 0;
+		while (i < args.Length) {
+			switch (args [i]) {
+			case "--list-sections":
+				ListSectionsAndExit ();
+				return;
+
+			case "--help":
+			case "--usage":
+				PrintUsageAndExit ();
+				return;
+
+			default:
+				break;
+			}
+			++i;
+		}
+
+		Conf.Load ();
+
+		string sectionname = args [0];
+
+		if (! Conf.Sections.ContainsKey (sectionname)) {
+			Console.Error.WriteLine ("ERROR: Invalid section name '{0}'", sectionname);
+			Environment.Exit (-1);
+		}
+
+		// Find all of the methods ("options") inside the specified section
+		// object which have the SectionOption attribute.
+		object section = Conf.Sections [sectionname];
+		MemberInfo [] members = section.GetType ().GetMembers ();
+		Hashtable options = new Hashtable ();
+
+		foreach (MemberInfo member in members) {
+			object [] attrs = member.GetCustomAttributes (typeof (Conf.SectionOption), false);
+			if (attrs.Length == 0)
+				continue;
+
+			// Store the name and description in a hashtable
+			options.Add (member.Name, ((Conf.SectionOption)attrs[0]).Description);
+		}
+	
+		// No option specified?
+		if (args.Length == 1)
+			ListSectionOptionsAndExit (sectionname, options);
+		
+		string optionname = args [1];
+		if (! options.ContainsKey (optionname)) {
+			Console.Error.WriteLine ("ERROR: Invalid option name '{0}'", optionname);
+			Environment.Exit (-2);
+		}
+
+		//
+		// Invoke the method the user has chosen
+		//
+
+		// Pack the remaining command line params into an array used for
+		// params of the method.
+		string [] optionparams = new string [args.Length - 2];
+		int j, k;
+		for (j = 0, k = 2; k < args.Length; j++, k++)
+			optionparams [j] = args [k];
+
+		// Invoke the method
+		string output = null;
+		bool result = Conf.InvokeOption (section, optionname, optionparams, out output);
+
+		// Read the result and show the output
+		if (result == true)
+			Console.WriteLine (output);
+		else {
+			Console.Error.WriteLine ("ERROR: Command execution failed.");
+			Console.Error.WriteLine (output);
+			Environment.Exit (-3);
+		}
+
+		Environment.Exit (0);
+
+	}
+
+
+}
diff -urNpX dontdiff beagle-orig/tools/Makefile.am beagle/tools/Makefile.am
--- beagle-orig/tools/Makefile.am	2005-03-31 20:16:20.000000000 +0100
+++ beagle/tools/Makefile.am	2005-04-14 22:51:25.000000000 +0100
@@ -19,6 +19,16 @@ WRAPPER_SED = sed 					\
 	-e "s|\@libdir\@|$(libdir)|g"			\
 	-e "s|\@gacprefix\@|$(GAC_PREFIX)|g"
 
+CONFIG_TARGET  = Config.exe
+CONFIG_WRAPPER = beagle-config
+CONFIG_CSFILES = $(srcdir)/Config.cs
+
+$(CONFIG_TARGET): $(CONFIG_CSFILES) $(LOCAL_ASSEMBLIES)
+	$(CSC) -o $@ $(CSFLAGS) $(CONFIG_CSFILES) $(ASSEMBLIES)
+
+$(CONFIG_WRAPPER): $(WRAPPER_IN)
+	$(WRAPPER_SED) -e "s|\@target\@|$(CONFIG_TARGET)|g" < $(srcdir)/$(WRAPPER_IN) > $@
+	chmod +x $(CONFIG_WRAPPER)
 
 QUERY_TARGET  = Query.exe
 QUERY_WRAPPER = beagle-query
@@ -43,10 +53,12 @@ $(EXERCISE_FILE_SYSTEM_WRAPPER): $(WRAPP
 	chmod +x $(EXERCISE_FILE_SYSTEM_WRAPPER)
 
 TARGETS = 				\
+	$(CONFIG_TARGET)		\
 	$(QUERY_TARGET)                 \
 	$(EXERCISE_FILE_SYSTEM_TARGET)
 
 WRAPPERS = 				\
+	$(CONFIG_WRAPPER)		\
 	$(QUERY_WRAPPER)		\
 	$(EXERCISE_FILE_SYSTEM_WRAPPER)
 
@@ -58,6 +70,7 @@ bin_SCRIPTS = 			\
 	beagle-status
 
 man_MANS = 			\
+	beagle-config.1		\
 	beagle-query.1		\
 	beagle-shutdown.1	\
 	beagle-status.1
diff -urNpX dontdiff beagle-orig/Util/Conf.cs beagle/Util/Conf.cs
--- beagle-orig/Util/Conf.cs	1970-01-01 01:00:00.000000000 +0100
+++ beagle/Util/Conf.cs	2005-04-14 22:47:58.000000000 +0100
@@ -0,0 +1,182 @@
+//
+// Conf.cs
+//
+// Copyright (C) 2005 Novell, Inc.
+//
+
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+//
+
+using System;
+using System.ComponentModel;
+using System.Collections;
+using System.IO;
+using System.Text;
+using System.Reflection;
+using Beagle.Util;
+
+namespace Beagle.Util {
+
+	public static class Conf {
+
+		private static GConf.Client client;
+		private static readonly string gconf_base = "/apps/beagle/";
+
+		private static IndexingSection indexing;
+		public static IndexingSection Indexing {
+			get { return indexing; }
+		}
+
+		private static Hashtable sections;
+		public static Hashtable Sections {
+			get { return sections; }
+		}
+
+		static Conf ()
+		{
+			client = new GConf.Client ();
+			sections = new Hashtable (1);
+			indexing = new IndexingSection ();
+
+			sections.Add ("indexing", indexing);
+		}
+
+		public static void Load  ()
+		{
+			ProcessContent ();
+		}
+
+		private static void ProcessContent ()
+		{
+			foreach (object section in sections.Values)
+				((IConfSection) section).ProcessContent ();
+		}
+
+		public static bool InvokeOption (object section, string option, string [] args, out string output)
+		{
+			MethodInfo method = section.GetType ().GetMethod (option);
+
+			// Can't pass a ref/out in a MethodInfo.Invoke() call so we box the
+			// output in a single-element array.
+			string [] boxoutput = new string[1];
+			
+			object [] methodparams = { boxoutput, args };
+			bool result = (bool) method.Invoke (section, methodparams);
+			
+			output = boxoutput[0];
+			return result;
+		}
+		
+		public class IndexingSection : IConfSection {
+
+			private readonly string gconf_base = Conf.gconf_base + "indexing/";
+
+			private ArrayList roots;
+			private ArrayList ignorepatterns;
+			private bool indexhomedir;
+
+			public ArrayList IgnorePatterns {
+				get { return ArrayList.ReadOnly (ignorepatterns); }
+			}
+
+			public ArrayList Roots {
+				get { return ArrayList.ReadOnly (roots); }
+			}
+
+			public bool IndexHomeDir {
+				get { return indexhomedir; }
+			}
+
+			public void ProcessContent ()
+			{
+				try {
+					roots = new ArrayList ((ICollection) client.Get (this.gconf_base + "roots"));
+				} catch (GConf.NoSuchKeyException e) {
+					roots = new ArrayList ();
+				}
+
+				try {
+					ignorepatterns = new ArrayList ((ICollection) client.Get (this.gconf_base + "ignorepatterns"));
+				} catch (GConf.NoSuchKeyException e) {
+					ignorepatterns = new ArrayList ();
+				}
+
+				try {
+					indexhomedir = (bool) client.Get (this.gconf_base + "indexhomedir");
+				} catch (GConf.NoSuchKeyException e) {
+					indexhomedir = true; // index home by default
+				}
+			}
+
+			[SectionOption (Description="List the indexing roots")]
+			public bool ListRoots (string [] output, string [] args)
+			{
+				output [0] = "Current roots:\n";
+				if (this.indexhomedir == true)
+					output [0] += " - Your home directory\n";
+				foreach (string root in roots)
+					output [0] += " - " + root + "\n";
+
+				return true;
+			}
+
+			[SectionOption (Description="List filename patterns to be ignored")]
+			public bool ListIgnorePatterns (string [] output, string [] args)
+			{
+				output [0] = "Current user-specified ignore patterns:\n";
+				foreach (string pattern in ignorepatterns)
+					output [0] += " - " + pattern + "\n";
+
+				return true;
+			}
+
+			// FIXME: The update mechanism possibly needs more thought. This option is a preview only!
+			[SectionOption (Description="Add a root path to be indexed")]
+			public bool AddRoot (string [] output, string [] args)
+			{
+				if (args.Length != 1) {
+					output [0] = "AddRoot requires one parameter: A path";
+					return false;
+				}
+				
+				roots.Add (args [0]);
+				client.Set (this.gconf_base + "roots", roots);
+				output [0] = "Root added.";
+				return true;
+			}
+
+		}
+
+		public class SectionOption : Attribute {
+				
+			public string Description;
+			
+		}
+
+		public interface IConfSection {
+			
+			void ProcessContent ();
+
+		}
+
+	}
+
+}
+
diff -urNpX dontdiff beagle-orig/Util/Makefile.am beagle/Util/Makefile.am
--- beagle-orig/Util/Makefile.am	2005-04-05 23:32:03.000000000 +0100
+++ beagle/Util/Makefile.am	2005-04-14 17:29:40.000000000 +0100
@@ -28,6 +28,7 @@ $(EXTSTR): $(EXTSTR_IN)
 CSFILES = 			        	\
 	$(srcdir)/camel.cs              	\
 	$(srcdir)/CommandLineFu.cs		\
+	$(srcdir)/Conf.cs			\
 	$(srcdir)/FileAdvise.cs			\
 	$(srcdir)/ExifData.cs			\
 	$(srcdir)/ExceptionHandlingThread.cs	\
