// // 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.Collections; using System.IO; using System.Diagnostics; using System.Reflection; using System.Xml.Serialization; using System.Text.RegularExpressions; using Beagle.Util; namespace Beagle.Util { public class Conf { // No instantiation private Conf () { } public static Hashtable Sections; public static IndexingConfig Indexing = null; public static DaemonConfig Daemon = null; public static SearchingConfig Searching = null; public static NetworkingConfig Networking = null; private static string configs_dir; private static Hashtable mtimes; private static Hashtable subscriptions; private static bool watching_for_updates; private static bool update_watch_present; private static BindingFlags method_search_flags = BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.InvokeMethod; public delegate void ConfigUpdateHandler (Section section); static Conf () { Sections = new Hashtable (3); mtimes = new Hashtable (3); subscriptions = new Hashtable (3); configs_dir = Path.Combine (PathFinder.StorageDir, "config"); if (!Directory.Exists (configs_dir)) Directory.CreateDirectory (configs_dir); Conf.Load (); } public static void WatchForUpdates () { // Make sure we don't try and watch for updates more than once if (update_watch_present) return; if (Inotify.Enabled) { Inotify.Subscribe (configs_dir, OnInotifyEvent, Inotify.EventType.Create | Inotify.EventType.CloseWrite); } else { // Poll for updates every 60 secs GLib.Timeout.Add (60000, new GLib.TimeoutHandler (CheckForUpdates)); } update_watch_present = true; } private static void OnInotifyEvent (Inotify.Watch watch, string path, string subitem, string srcpath, Inotify.EventType type) { if (subitem == "" || watching_for_updates == false) return; Load (); } private static bool CheckForUpdates () { if (watching_for_updates) Load (); return true; } public static void Subscribe (Type type, ConfigUpdateHandler callback) { if (!subscriptions.ContainsKey (type)) subscriptions.Add (type, new ArrayList (1)); ArrayList callbacks = (ArrayList) subscriptions [type]; callbacks.Add (callback); } private static void NotifySubscribers (Section section) { Type type = section.GetType (); ArrayList callbacks = (ArrayList) subscriptions [type]; if (callbacks == null) return; foreach (ConfigUpdateHandler callback in callbacks) callback (section); } public static void Load () { Load (false); } public static void Load (bool force) { Section temp; // FIXME: Yeah LoadFile (typeof (IndexingConfig), Indexing, out temp, force); Indexing = (IndexingConfig) temp; NotifySubscribers (Indexing); LoadFile (typeof (DaemonConfig), Daemon, out temp, force); Daemon = (DaemonConfig) temp; NotifySubscribers (Daemon); LoadFile (typeof (SearchingConfig), Searching, out temp, force); Searching = (SearchingConfig) temp; NotifySubscribers (Searching); LoadFile (typeof (NetworkingConfig), Networking, out temp, force); Networking = (NetworkingConfig) temp; NotifySubscribers (Networking); watching_for_updates = true; } public static void Save () { Save (false); } public static void Save (bool force) { foreach (Section section in Sections.Values) if (force || section.SaveNeeded) SaveFile (section); } private static bool LoadFile (Type type, Section current, out Section section, bool force) { section = current; object [] attrs = Attribute.GetCustomAttributes (type, typeof (ConfigSection)); if (attrs.Length == 0) throw new ConfigException ("Could not find ConfigSection attribute on " + type); string sectionname = ((ConfigSection) attrs [0]).Name; string filename = sectionname + ".xml"; string filepath = Path.Combine (configs_dir, filename); if (!File.Exists (filepath)) { if (current == null) ConstructDefaultSection (type, sectionname, out section); return false; } if (!force && current != null && mtimes.ContainsKey (sectionname) && File.GetLastWriteTimeUtc (filepath).CompareTo ((DateTime) mtimes [sectionname]) <= 0) return false; Logger.Log.Debug ("Loading {0} from {1}", type, filename); FileStream fs = null; try { fs = File.Open (filepath, FileMode.Open, FileAccess.Read, FileShare.Read); XmlSerializer serializer = new XmlSerializer (type); section = (Section) serializer.Deserialize (fs); } catch (Exception e) { Logger.Log.Error ("Could not load configuration from {0}: {1}", filename, e.Message); if (fs != null) fs.Close (); if (current == null) ConstructDefaultSection (type, sectionname, out section); return false; } fs.Close (); Sections.Remove (sectionname); Sections.Add (sectionname, section); mtimes.Remove (sectionname); mtimes.Add (sectionname, File.GetLastWriteTimeUtc (filepath)); return true; } private static bool SaveFile (Section section) { Type type = section.GetType (); object [] attrs = Attribute.GetCustomAttributes (type, typeof (ConfigSection)); if (attrs.Length == 0) throw new ConfigException ("Could not find ConfigSection attribute on " + type); string sectionname = ((ConfigSection) attrs [0]).Name; string filename = sectionname + ".xml"; string filepath = Path.Combine (configs_dir, filename); Logger.Log.Debug ("Saving {0} to {1}", type, filename); FileStream fs = null; try { watching_for_updates = false; fs = new FileStream (filepath, FileMode.Create); XmlSerializer serializer = new XmlSerializer (type); XmlFu.SerializeUtf8 (serializer, fs, section); } catch (Exception e) { if (fs != null) fs.Close (); Logger.Log.Error ("Could not save configuration to {0}: {1}", filename, e); watching_for_updates = true; return false; } fs.Close (); mtimes.Remove (sectionname); mtimes.Add (sectionname, File.GetLastWriteTimeUtc (filepath)); watching_for_updates = true; return true; } private static void ConstructDefaultSection (Type type, string sectionname, out Section section) { ConstructorInfo ctor = type.GetConstructor (Type.EmptyTypes); section = (Section) ctor.Invoke (null); Sections.Remove (sectionname); Sections.Add (sectionname, section); } // Lists all config file options in a hash table where key is option name, // and value is description. public static Hashtable GetOptions (Section section) { Hashtable options = new Hashtable (); MemberInfo [] members = section.GetType ().GetMembers (method_search_flags); // Find all of the methods ("options") inside the specified section // object which have the ConfigOption attribute. foreach (MemberInfo member in members) { object [] attrs = member.GetCustomAttributes (typeof (ConfigOption), false); if (attrs.Length > 0) options.Add (member.Name, ((ConfigOption) attrs [0]).Description); } return options; } public static bool InvokeOption (Section section, string option, string [] args, out string output) { MethodInfo method = section.GetType ().GetMethod (option, method_search_flags); if (method == null) { string msg = String.Format ("No such method '{0}' for section '{1}'", option, section); throw new ConfigException(msg); } object [] attrs = method.GetCustomAttributes (typeof (ConfigOption), false); if (attrs.Length == 0) { string msg = String.Format ("Method '{0}' is not a configurable option", option); throw new ConfigException (msg); } // Check the required number of parameters have been provided ConfigOption attr = (ConfigOption) attrs [0]; if (attr.Params > 0 && args.Length < attr.Params) { string msg = String.Format ("Option '{0}' requires {1} parameter(s): {2}", option, attr.Params, attr.ParamsDescription); throw new ConfigException (msg); } object [] methodparams = { null, args }; bool result = (bool) method.Invoke (section, methodparams); output = (string) methodparams [0]; // Mark the section as save-needed if we just changed something if (result && attr.IsMutator) section.SaveNeeded = true; return result; } [ConfigSection (Name="searching")] public class SearchingConfig : Section { private bool autostart = true; public bool Autostart { get { return autostart; } set { autostart = value; } } private KeyBinding show_search_window_binding = new KeyBinding ("F12"); public KeyBinding ShowSearchWindowBinding { get { return show_search_window_binding; } set { show_search_window_binding = value; } } private int max_displayed = 5; public int MaxDisplayed { get { return max_displayed; } set { if (value <= 0) max_displayed = 1; else max_displayed = value; } } // BeagleSearch window position and dimension // stored as percentage of screen co-ordinates // to deal with change of resolution problem - hints from tberman private float beagle_search_pos_x = 0; public float BeaglePosX { get { return beagle_search_pos_x; } set { beagle_search_pos_x = value; } } private float beagle_search_pos_y = 0; public float BeaglePosY { get { return beagle_search_pos_y; } set { beagle_search_pos_y = value; } } private float beagle_search_width = 0; public float BeagleSearchWidth { get { return beagle_search_width; } set { beagle_search_width = value; } } private float beagle_search_height = 0; public float BeagleSearchHeight { get { return beagle_search_height; } set { beagle_search_height = value; } } // ah!We want a Queue but Queue doesnt serialize *easily* private ArrayList search_history = new ArrayList (); public ArrayList SearchHistory { get { return search_history; } set { search_history = value; } } } [ConfigSection (Name="daemon")] public class DaemonConfig : Section { private ArrayList static_queryables = new ArrayList (); public ArrayList StaticQueryables { get { return static_queryables; } set { static_queryables = value; } } // By default, every backend is allowed. // Only maintain a list of denied backends. private ArrayList denied_backends = new ArrayList (); public ArrayList DeniedBackends { get { return denied_backends; } set { denied_backends = value; } } private bool allow_static_backend = false; // by default, false public bool AllowStaticBackend { get { return allow_static_backend; } // Don't really want to expose this, but serialization requires it set { allow_static_backend = value; } } private bool index_synchronization = true; public bool IndexSynchronization { get { return index_synchronization; } // Don't really want to expose this, but serialization requires it set { index_synchronization = value; } } [ConfigOption (Description="Enable a backend", Params=1, ParamsDescription="Name of the backend to enable")] internal bool AllowBackend (out string output, string [] args) { denied_backends.Remove (args [0]); output = "Backend allowed (need to restart beagled for changes to take effect)."; return true; } [ConfigOption (Description="Disable a backend", Params=1, ParamsDescription="Name of the backend to disable")] internal bool DenyBackend (out string output, string [] args) { denied_backends.Add (args [0]); output = "Backend disabled (need to restart beagled for changes to take effect)."; return true; } private bool allow_root = false; public bool AllowRoot { get { return allow_root; } set { allow_root = value; } } [ConfigOption (Description="Add a static queryable", Params=1, ParamsDescription="Index path")] internal bool AddStaticQueryable (out string output, string [] args) { static_queryables.Add (args [0]); output = "Static queryable added."; return true; } [ConfigOption (Description="Remove a static queryable", Params=1, ParamsDescription="Index path")] internal bool DelStaticQueryable (out string output, string [] args) { static_queryables.Remove (args [0]); output = "Static queryable removed."; return true; } [ConfigOption (Description="List user-specified static queryables", IsMutator=false)] internal bool ListStaticQueryables (out string output, string [] args) { output = "User-specified static queryables:\n"; foreach (string index_path in static_queryables) output += String.Format (" - {0}\n", index_path); return true; } [ConfigOption (Description="Toggles whether static indexes will be enabled")] internal bool ToggleAllowStaticBackend (out string output, string [] args) { allow_static_backend = !allow_static_backend; output = "Static indexes are " + ((allow_static_backend) ? "enabled" : "disabled") + " (need to restart beagled for changes to take effect)."; return true; } [ConfigOption (Description="Toggles whether your indexes will be synchronized locally if your home directory is on a network device (eg. NFS/Samba)")] internal bool ToggleIndexSynchronization (out string output, string [] args) { index_synchronization = !index_synchronization; output = "Index Synchronization is " + ((index_synchronization) ? "enabled" : "disabled") + "."; return true; } [ConfigOption (Description="Toggles whether Beagle can be run as root")] internal bool ToggleAllowRoot (out string output, string [] args) { allow_root = ! allow_root; if (allow_root) output = "Beagle is now permitted to run as root"; else output = "Beagle is no longer permitted to run as root"; return true; } } [ConfigSection (Name="indexing")] public class IndexingConfig : Section { private ArrayList roots = new ArrayList (); [XmlArray] [XmlArrayItem(ElementName="Root", Type=typeof(string))] public ArrayList Roots { get { return roots; } set { roots = value; } } private bool index_home_dir = true; public bool IndexHomeDir { get { return index_home_dir; } set { index_home_dir = value; } } private ArrayList excludes = new ArrayList (); [XmlArray] [XmlArrayItem (ElementName="ExcludeItem", Type=typeof(ExcludeItem))] public ArrayList Excludes { get { return excludes; } set { excludes = value; } } [ConfigOption (Description="List the indexing roots", IsMutator=false)] internal bool ListRoots (out string output, string [] args) { output = "Current roots:\n"; if (this.index_home_dir == true) output += " - Your home directory\n"; foreach (string root in roots) output += " - " + root + "\n"; return true; } [ConfigOption (Description="Toggles whether your home directory is to be indexed as a root")] internal bool IndexHome (out string output, string [] args) { if (index_home_dir) output = "Your home directory will not be indexed."; else output = "Your home directory will be indexed."; index_home_dir = !index_home_dir; return true; } [ConfigOption (Description="Add a root path to be indexed", Params=1, ParamsDescription="A path")] internal bool AddRoot (out string output, string [] args) { roots.Add (args [0]); output = "Root added."; return true; } [ConfigOption (Description="Remove an indexing root", Params=1, ParamsDescription="A path")] internal bool DelRoot (out string output, string [] args) { roots.Remove (args [0]); output = "Root removed."; return true; } [ConfigOption (Description="List user-specified resources to be excluded from indexing", IsMutator=false)] internal bool ListExcludes (out string output, string [] args) { output = "User-specified resources to be excluded from indexing:\n"; foreach (ExcludeItem exclude_item in excludes) output += String.Format (" - [{0}] {1}\n", exclude_item.Type.ToString (), exclude_item.Value); return true; } [ConfigOption (Description="Add a resource to exclude from indexing", Params=2, ParamsDescription="A type [path/pattern/mailfolder], a path/pattern/name")] internal bool AddExclude (out string output, string [] args) { ExcludeType type; try { type = (ExcludeType) Enum.Parse (typeof (ExcludeType), args [0], true); } catch (Exception e) { output = String.Format("Invalid type '{0}'. Valid types: Path, Pattern, MailFolder", args [0]); return false; } excludes.Add (new ExcludeItem (type, args [1])); output = "Exclude added."; return true; } [ConfigOption (Description="Remove an excluded resource", Params=2, ParamsDescription="A type [path/pattern/mailfolder], a path/pattern/name")] internal bool DelExclude (out string output, string [] args) { ExcludeType type; try { type = (ExcludeType) Enum.Parse (typeof (ExcludeType), args [0], true); } catch (Exception e) { output = String.Format("Invalid type '{0}'. Valid types: Path, Pattern, MailFolder", args [0]); return false; } foreach (ExcludeItem item in excludes) { if (item.Type != type || item.Value != args [1]) continue; excludes.Remove (item); output = "Exclude removed."; return true; } output = "Could not find requested exclude to remove."; return false; } } [ConfigSection (Name="networking")] public class NetworkingConfig: Section { private ArrayList neighborhoodNodes = new ArrayList (); private ArrayList globalNodes = new ArrayList (); [XmlArray] [XmlArrayItem(ElementName="NeighborhoodNodes", Type=typeof(string))] public ArrayList NeighborhoodNodes { get { return neighborhoodNodes; } set { neighborhoodNodes = value; } } [XmlArray] [XmlArrayItem(ElementName="GlobalNodes", Type=typeof(string))] public ArrayList GlobalNodes { get { return globalNodes; } set { globalNodes = value; } } [ConfigOption (Description="List Networked Beagle Daemons to query", IsMutator=false)] internal bool ListBeagleNodes (out string output, string [] args) { output = "Current list of Networked Beagle Daemons to query:\n"; output += "Neighborhood Domain:\n"; foreach (string nb in neighborhoodNodes) output += " - " + nb + "\n"; output += "\nGlobal Domain:\n"; foreach (string nb in globalNodes) output += " - " + nb + "\n"; return true; } [ConfigOption (Description="Add a Networked Beagle Daemon to the 'Neighborhood' domain", Params=1, ParamsDescription="HostName:PortNo")] internal bool AddNeighborhoodBeagleNode (out string output, string [] args) { string node = args[0]; if (((string[])node.Split(':')).Length < 2) node = args [0].Trim() + ":4000"; neighborhoodNodes.Add(node); output = "Networked Beagle Daemon \"" + node +"\" added to Neighborhood."; return true; } [ConfigOption (Description="Add a Networked Beagle Daemon to the 'Global' domain", Params=1, ParamsDescription="HostName:PortNo")] internal bool AddGlobalBeagleNode (out string output, string [] args) { string node = args[0]; if (((string[])node.Split(':')).Length < 2) node = args [0].Trim() + ":4000"; globalNodes.Add(node); output = "Networked Beagle Daemon \"" + node +"\" added to Global."; return true; } [ConfigOption (Description="Remove a configured Networked Beagle Daemon from the 'Neighborhood' domain.", Params=1, ParamsDescription="HostName:PortNo")] internal bool DelNeighborhoodBeagleNode (out string output, string [] args) { string node = args[0]; if (((string[])node.Split(':')).Length < 2) node = args [0].Trim() + ":4000"; neighborhoodNodes.Remove(node); output = "Networked Beagle Daemon \"" + node +"\" removed from Neighborhood."; return true; } [ConfigOption (Description="Remove a configured Networked Beagle Daemon from the 'Neighborhood' domain.", Params=1, ParamsDescription="HostName:PortNo")] internal bool DelGlobalBeagleNode (out string output, string [] args) { string node = args[0]; if (((string[])node.Split(':')).Length < 2) node = args [0].Trim() + ":4000"; globalNodes.Remove(node); output = "Networked Beagle Daemon \"" + node +"\" removed from Global."; return true; } [ConfigOption (Description="Check current configuration of remote access to Beagle", IsMutator=false)] internal bool CheckRemoteAccess(out string output, string [] args) { if (allowRemoteAccess) output = "Remote Access to Beagle is currently ENABLED."; else output = "Remote Access to Beagle is currently DISABLED."; return true; } private bool allowRemoteAccess = true; public bool AllowRemoteAccess { get { return allowRemoteAccess; } set { allowRemoteAccess = value; } } [ConfigOption (Description="Enable/DisableRemote access to Beagle")] internal bool SwitchRemoteAccess (out string output, string [] args) { allowRemoteAccess = !allowRemoteAccess; if (allowRemoteAccess) output = "Remote Access to Beagle now ENABLED."; else output = "Remote Access to Beagle now DISABLED."; return true; } } //#endif public class Section { [XmlIgnore] public bool SaveNeeded = false; } private class ConfigOption : Attribute { public string Description; public int Params; public string ParamsDescription; public bool IsMutator = true; } private class ConfigSection : Attribute { public string Name; } public class ConfigException : Exception { public ConfigException (string msg) : base (msg) { } } } ////////////////////////////////////////////////////////////////////// public enum ExcludeType { Path, Pattern, MailFolder } public class ExcludeItem { private ExcludeType type; private string val; [XmlAttribute] public ExcludeType Type { get { return type; } set { type = value; } } private string exactMatch; private string prefix; private string suffix; private Regex regex; [XmlAttribute] public string Value { get { return val; } set { switch (type) { case ExcludeType.Path: case ExcludeType.MailFolder: prefix = value; break; case ExcludeType.Pattern: if (value.StartsWith ("/") && value.EndsWith ("/")) { regex = new Regex (value.Substring (1, value.Length - 2)); break; } int i = value.IndexOf ('*'); if (i == -1) { exactMatch = value; } else { if (i > 0) prefix = value.Substring (0, i); if (i < value.Length-1) suffix = value.Substring (i+1); } break; } val = value; } } public ExcludeItem () {} public ExcludeItem (ExcludeType type, string value) { this.Type = type; this.Value = value; } public bool IsMatch (string param) { switch (Type) { case ExcludeType.Path: case ExcludeType.MailFolder: if (prefix != null && ! param.StartsWith (prefix)) return false; return true; case ExcludeType.Pattern: if (exactMatch != null) return param == exactMatch; if (prefix != null && ! param.StartsWith (prefix)) return false; if (suffix != null && ! param.EndsWith (suffix)) return false; if (regex != null && ! regex.IsMatch (param)) return false; return true; } return false; } public override bool Equals (object obj) { ExcludeItem exclude = obj as ExcludeItem; return (exclude != null && exclude.Type == type && exclude.Value == val); } public override int GetHashCode () { return (this.Value.GetHashCode () ^ (int) this.Type); } } ////////////////////////////////////////////////////////////////////// public class KeyBinding { public string Key; [XmlAttribute] public bool Ctrl = false; [XmlAttribute] public bool Alt = false; public KeyBinding () {} public KeyBinding (string key) : this (key, false, false) {} public KeyBinding (string key, bool ctrl, bool alt) { Key = key; Ctrl = ctrl; Alt = alt; } public override string ToString () { string result = ""; if (Ctrl) result += ""; if (Alt) result += ""; result += Key; return result; } public string ToReadableString () { return ToString ().Replace (">", "-").Replace ("<", ""); } } }