Compute lucene-style scores for our hits.
[beagle.git] / Util / Conf.cs
blobc452c5a3abb2127d77dc9463ea5586d248452ae9
1 //
2 // Conf.cs
3 //
4 // Copyright (C) 2005 Novell, Inc.
5 //
7 //
8 // Permission is hereby granted, free of charge, to any person obtaining a copy
9 // of this software and associated documentation files (the "Software"), to deal
10 // in the Software without restriction, including without limitation the rights
11 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 // copies of the Software, and to permit persons to whom the Software is
13 // furnished to do so, subject to the following conditions:
15 // The above copyright notice and this permission notice shall be included in all
16 // copies or substantial portions of the Software.
18 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 // SOFTWARE.
27 using System;
28 using System.Collections;
29 using System.IO;
30 using System.Reflection;
31 using System.Xml.Serialization;
32 using System.Text.RegularExpressions;
34 using Beagle.Util;
36 namespace Beagle.Util {
38 public class Conf {
40 // No instantiation
41 private Conf () { }
43 public static Hashtable Sections;
45 public static IndexingConfig Indexing = null;
46 public static DaemonConfig Daemon = null;
47 public static SearchingConfig Searching = null;
49 //#if ENABLE_WEBSERVICES
50 public static NetworkingConfig Networking = null;
51 public static WebServicesConfig WebServices = null;
52 //#endif
53 private static string configs_dir;
54 private static Hashtable mtimes;
55 private static Hashtable subscriptions;
57 private static bool watching_for_updates;
58 private static bool update_watch_present;
60 private static BindingFlags method_search_flags = BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.InvokeMethod;
62 public delegate void ConfigUpdateHandler (Section section);
64 static Conf ()
66 Sections = new Hashtable (3);
67 mtimes = new Hashtable (3);
68 subscriptions = new Hashtable (3);
70 configs_dir = Path.Combine (PathFinder.StorageDir, "config");
71 if (!Directory.Exists (configs_dir))
72 Directory.CreateDirectory (configs_dir);
74 Conf.Load ();
77 public static void WatchForUpdates ()
79 // Make sure we don't try and watch for updates more than once
80 if (update_watch_present)
81 return;
83 if (Inotify.Enabled) {
84 Inotify.Subscribe (configs_dir, OnInotifyEvent, Inotify.EventType.Create | Inotify.EventType.CloseWrite);
85 } else {
86 // Poll for updates every 60 secs
87 GLib.Timeout.Add (60000, new GLib.TimeoutHandler (CheckForUpdates));
90 update_watch_present = true;
93 private static void OnInotifyEvent (Inotify.Watch watch, string path, string subitem, string srcpath, Inotify.EventType type)
95 if (subitem == "" || watching_for_updates == false)
96 return;
98 Load ();
101 private static bool CheckForUpdates ()
103 if (watching_for_updates)
104 Load ();
105 return true;
108 public static void Subscribe (Type type, ConfigUpdateHandler callback)
110 if (!subscriptions.ContainsKey (type))
111 subscriptions.Add (type, new ArrayList (1));
113 ArrayList callbacks = (ArrayList) subscriptions [type];
114 callbacks.Add (callback);
117 private static void NotifySubscribers (Section section)
119 Type type = section.GetType ();
120 ArrayList callbacks = (ArrayList) subscriptions [type];
122 if (callbacks == null)
123 return;
125 foreach (ConfigUpdateHandler callback in callbacks)
126 callback (section);
129 public static void Load ()
131 Load (false);
134 public static void Load (bool force)
136 Section temp;
138 // FIXME: Yeah
139 LoadFile (typeof (IndexingConfig), Indexing, out temp, force);
140 Indexing = (IndexingConfig) temp;
141 NotifySubscribers (Indexing);
143 LoadFile (typeof (DaemonConfig), Daemon, out temp, force);
144 Daemon = (DaemonConfig) temp;
145 NotifySubscribers (Daemon);
147 LoadFile (typeof (SearchingConfig), Searching, out temp, force);
148 Searching = (SearchingConfig) temp;
149 NotifySubscribers (Searching);
151 //#if ENABLE_WEBSERVICES
152 LoadFile (typeof (NetworkingConfig), Networking, out temp, force);
153 Networking = (NetworkingConfig) temp;
154 NotifySubscribers (Networking);
156 LoadFile (typeof (WebServicesConfig), WebServices, out temp, force);
157 WebServices = (WebServicesConfig) temp;
158 NotifySubscribers (WebServices);
159 //#endif
161 watching_for_updates = true;
164 public static void Save ()
166 Save (false);
169 public static void Save (bool force)
171 foreach (Section section in Sections.Values)
172 if (force || section.SaveNeeded)
173 SaveFile (section);
176 private static bool LoadFile (Type type, Section current, out Section section, bool force)
178 section = current;
179 object [] attrs = Attribute.GetCustomAttributes (type, typeof (ConfigSection));
180 if (attrs.Length == 0)
181 throw new ConfigException ("Could not find ConfigSection attribute on " + type);
183 string sectionname = ((ConfigSection) attrs [0]).Name;
184 string filename = sectionname + ".xml";
185 string filepath = Path.Combine (configs_dir, filename);
186 if (!File.Exists (filepath)) {
187 if (current == null)
188 ConstructDefaultSection (type, sectionname, out section);
189 return false;
192 if (!force && current != null && mtimes.ContainsKey (sectionname) &&
193 File.GetLastWriteTimeUtc (filepath).CompareTo ((DateTime) mtimes [sectionname]) <= 0)
194 return false;
196 Logger.Log.Debug ("Loading {0} from {1}", type, filename);
197 FileStream fs = null;
199 try {
200 fs = File.Open (filepath, FileMode.Open, FileAccess.Read, FileShare.Read);
201 XmlSerializer serializer = new XmlSerializer (type);
202 section = (Section) serializer.Deserialize (fs);
203 } catch (Exception e) {
204 Logger.Log.Error ("Could not load configuration from {0}: {1}", filename, e.Message);
205 if (fs != null)
206 fs.Close ();
207 if (current == null)
208 ConstructDefaultSection (type, sectionname, out section);
209 return false;
212 fs.Close ();
213 Sections.Remove (sectionname);
214 Sections.Add (sectionname, section);
215 mtimes.Remove (sectionname);
216 mtimes.Add (sectionname, File.GetLastWriteTimeUtc (filepath));
217 return true;
220 private static bool SaveFile (Section section)
222 Type type = section.GetType ();
223 object [] attrs = Attribute.GetCustomAttributes (type, typeof (ConfigSection));
224 if (attrs.Length == 0)
225 throw new ConfigException ("Could not find ConfigSection attribute on " + type);
227 string sectionname = ((ConfigSection) attrs [0]).Name;
228 string filename = sectionname + ".xml";
229 string filepath = Path.Combine (configs_dir, filename);
231 Logger.Log.Debug ("Saving {0} to {1}", type, filename);
232 FileStream fs = null;
234 try {
235 watching_for_updates = false;
236 fs = new FileStream (filepath, FileMode.Create);
237 XmlSerializer serializer = new XmlSerializer (type);
238 serializer.Serialize (fs, section);
239 } catch (Exception e) {
240 if (fs != null)
241 fs.Close ();
242 Logger.Log.Error ("Could not save configuration to {0}: {1}", filename, e);
243 watching_for_updates = true;
244 return false;
247 fs.Close ();
248 mtimes.Remove (sectionname);
249 mtimes.Add (sectionname, File.GetLastWriteTimeUtc (filepath));
250 watching_for_updates = true;
251 return true;
254 private static void ConstructDefaultSection (Type type, string sectionname, out Section section)
256 ConstructorInfo ctor = type.GetConstructor (Type.EmptyTypes);
257 section = (Section) ctor.Invoke (null);
258 Sections.Remove (sectionname);
259 Sections.Add (sectionname, section);
262 // Lists all config file options in a hash table where key is option name,
263 // and value is description.
264 public static Hashtable GetOptions (Section section)
266 Hashtable options = new Hashtable ();
267 MemberInfo [] members = section.GetType ().GetMembers (method_search_flags);
269 // Find all of the methods ("options") inside the specified section
270 // object which have the ConfigOption attribute.
271 foreach (MemberInfo member in members) {
272 object [] attrs = member.GetCustomAttributes (typeof (ConfigOption), false);
273 if (attrs.Length > 0)
274 options.Add (member.Name, ((ConfigOption) attrs [0]).Description);
277 return options;
280 public static bool InvokeOption (Section section, string option, string [] args, out string output)
282 MethodInfo method = section.GetType ().GetMethod (option, method_search_flags);
283 if (method == null) {
284 string msg = String.Format ("No such method '{0}' for section '{1}'", option, section);
285 throw new ConfigException(msg);
287 object [] attrs = method.GetCustomAttributes (typeof (ConfigOption), false);
288 if (attrs.Length == 0) {
289 string msg = String.Format ("Method '{0}' is not a configurable option", option);
290 throw new ConfigException (msg);
293 // Check the required number of parameters have been provided
294 ConfigOption attr = (ConfigOption) attrs [0];
295 if (attr.Params > 0 && args.Length < attr.Params) {
296 string msg = String.Format ("Option '{0}' requires {1} parameter(s): {2}", option, attr.Params, attr.ParamsDescription);
297 throw new ConfigException (msg);
300 object [] methodparams = { null, args };
301 bool result = (bool) method.Invoke (section, methodparams);
302 output = (string) methodparams [0];
304 // Mark the section as save-needed if we just changed something
305 if (result && attr.IsMutator)
306 section.SaveNeeded = true;
308 return result;
311 [ConfigSection (Name="searching")]
312 public class SearchingConfig : Section {
314 private bool autostart = true;
315 public bool Autostart {
316 get { return autostart; }
317 set { autostart = value; }
320 private KeyBinding show_search_window_binding = new KeyBinding ("F12");
321 public KeyBinding ShowSearchWindowBinding {
322 get { return show_search_window_binding; }
323 set { show_search_window_binding = value; }
327 [ConfigSection (Name="daemon")]
328 public class DaemonConfig : Section {
329 private ArrayList static_queryables = new ArrayList ();
330 public ArrayList StaticQueryables {
331 get { return static_queryables; }
332 set { static_queryables = value; }
335 private ArrayList allowed_backends = new ArrayList ();
336 public ArrayList AllowedBackends {
337 get { return allowed_backends; }
338 set { allowed_backends = value; }
341 private ArrayList denied_backends = new ArrayList ();
342 public ArrayList DeniedBackends {
343 get { return denied_backends; }
344 set { denied_backends = value; }
347 private bool index_synchronization = true;
348 public bool IndexSynchronization {
349 get { return index_synchronization; }
350 // Don't really want to expose this, but serialization requires it
351 set { index_synchronization = value; }
354 [ConfigOption (Description="Add a static queryable", Params=1, ParamsDescription="Index path")]
355 internal bool AddStaticQueryable (out string output, string [] args)
357 static_queryables.Add (args [0]);
358 output = "Static queryable added.";
359 return true;
362 [ConfigOption (Description="Remove a static queryable", Params=1, ParamsDescription="Index path")]
363 internal bool DelStaticQueryable (out string output, string [] args)
365 static_queryables.Remove (args [0]);
366 output = "Static queryable removed.";
367 return true;
370 [ConfigOption (Description="List user-specified static queryables", IsMutator=false)]
371 internal bool ListStaticQueryables (out string output, string [] args)
373 output = "User-specified static queryables:\n";
374 foreach (string index_path in static_queryables)
375 output += String.Format (" - {0}\n", index_path);
376 return true;
379 [ConfigOption (Description="Toggles whether your indexes will be synchronized locally if your home directory is on a network device (eg. NFS/Samba)")]
380 internal bool ToggleIndexSynchronization (out string output, string [] args)
382 index_synchronization = !index_synchronization;
383 output = "Index Synchronization is " + ((index_synchronization) ? "enabled" : "disabled") + ".";
384 return true;
388 [ConfigSection (Name="indexing")]
389 public class IndexingConfig : Section
391 private ArrayList roots = new ArrayList ();
392 [XmlArray]
393 [XmlArrayItem(ElementName="Root", Type=typeof(string))]
394 public ArrayList Roots {
395 get { return ArrayList.ReadOnly (roots); }
396 set { roots = value; }
399 private bool index_home_dir = true;
400 public bool IndexHomeDir {
401 get { return index_home_dir; }
402 set { index_home_dir = value; }
405 private ArrayList excludes = new ArrayList ();
406 [XmlArray]
407 [XmlArrayItem (ElementName="ExcludeItem", Type=typeof(ExcludeItem))]
408 public ArrayList Excludes {
409 get { return ArrayList.ReadOnly (excludes); }
410 set { excludes = value; }
413 [ConfigOption (Description="List the indexing roots", IsMutator=false)]
414 internal bool ListRoots (out string output, string [] args)
416 output = "Current roots:\n";
417 if (this.index_home_dir == true)
418 output += " - Your home directory\n";
419 foreach (string root in roots)
420 output += " - " + root + "\n";
422 return true;
425 [ConfigOption (Description="Toggles whether your home directory is to be indexed as a root")]
426 internal bool IndexHome (out string output, string [] args)
428 if (index_home_dir)
429 output = "Your home directory will not be indexed.";
430 else
431 output = "Your home directory will be indexed.";
432 index_home_dir = !index_home_dir;
433 return true;
436 [ConfigOption (Description="Add a root path to be indexed", Params=1, ParamsDescription="A path")]
437 internal bool AddRoot (out string output, string [] args)
439 roots.Add (args [0]);
440 output = "Root added.";
441 return true;
444 [ConfigOption (Description="Remove an indexing root", Params=1, ParamsDescription="A path")]
445 internal bool DelRoot (out string output, string [] args)
447 roots.Remove (args [0]);
448 output = "Root removed.";
449 return true;
452 [ConfigOption (Description="List user-specified resources to be excluded from indexing", IsMutator=false)]
453 internal bool ListExcludes (out string output, string [] args)
455 output = "User-specified resources to be excluded from indexing:\n";
456 foreach (ExcludeItem exclude_item in excludes)
457 output += String.Format (" - [{0}] {1}\n", exclude_item.Type.ToString (), exclude_item.Value);
458 return true;
461 [ConfigOption (Description="Add a resource to exclude from indexing", Params=2, ParamsDescription="A type [path/pattern/mailfolder], a path/pattern/name")]
462 internal bool AddExclude (out string output, string [] args)
464 ExcludeType type;
465 try {
466 type = (ExcludeType) Enum.Parse (typeof (ExcludeType), args [0], true);
467 } catch (Exception e) {
468 output = String.Format("Invalid type '{0}'. Valid types: Path, Pattern, MailFolder", args [0]);
469 return false;
472 excludes.Add (new ExcludeItem (type, args [1]));
473 output = "Exclude added.";
474 return true;
477 [ConfigOption (Description="Remove an excluded resource", Params=2, ParamsDescription="A type [path/pattern/mailfolder], a path/pattern/name")]
478 internal bool DelExclude (out string output, string [] args)
480 ExcludeType type;
481 try {
482 type = (ExcludeType) Enum.Parse (typeof (ExcludeType), args [0], true);
483 } catch (Exception e) {
484 output = String.Format("Invalid type '{0}'. Valid types: Path, Pattern, MailFolder", args [0]);
485 return false;
488 foreach (ExcludeItem item in excludes) {
489 if (item.Type != type || item.Value != args [1])
490 continue;
491 excludes.Remove (item);
492 output = "Exclude removed.";
493 return true;
496 output = "Could not find requested exclude to remove.";
497 return false;
502 //#if ENABLE_WEBSERVICES
503 [ConfigSection (Name="webservices")]
504 public class WebServicesConfig: Section
506 private ArrayList publicFolders = new ArrayList ();
507 [XmlArray]
508 [XmlArrayItem(ElementName="PublicFolders", Type=typeof(string))]
509 public ArrayList PublicFolders {
510 get { return ArrayList.ReadOnly (publicFolders); }
511 set { publicFolders = value; }
514 private bool allowGlobalAccess = true;
515 public bool AllowGlobalAccess {
516 get { return allowGlobalAccess; }
517 set { allowGlobalAccess = value; }
520 [ConfigOption (Description="List the public folders", IsMutator=false)]
521 internal bool ListPublicFolders(out string output, string [] args)
523 output = "Current list of public folders:\n";
525 foreach (string pf in publicFolders)
526 output += " - " + pf + "\n";
528 return true;
531 [ConfigOption (Description="Check current configuration of global access to Beagle web-services", IsMutator=false)]
532 internal bool CheckGlobalAccess(out string output, string [] args)
534 if (allowGlobalAccess)
535 output = "Global Access to Beagle WebServices is currently ENABLED.";
536 else
537 output = "Global Access to Beagle WebServices is currently DISABLED.";
539 return true;
542 [ConfigOption (Description="Enable/Disable global access to Beagle web-services")]
543 internal bool SwitchGlobalAccess (out string output, string [] args)
545 allowGlobalAccess = !allowGlobalAccess;
547 if (allowGlobalAccess)
548 output = "Global Access to Beagle WebServices now ENABLED.";
549 else
550 output = "Global Access to Beagle WebServices now DISABLED.";
552 return true;
555 [ConfigOption (Description="Add public web-service access to a folder", Params=1, ParamsDescription="A path")]
556 internal bool AddPublicFolder (out string output, string [] args)
558 publicFolders.Add (args [0]);
559 output = "PublicFolder " + args[0] + " added.";
560 return true;
563 [ConfigOption (Description="Remove public web-service access to a folder", Params=1, ParamsDescription="A path")]
564 internal bool DelPublicFolder (out string output, string [] args)
566 publicFolders.Remove (args [0]);
567 output = "PublicFolder " + args[0] + " removed.";
568 return true;
572 [ConfigSection (Name="networking")]
573 public class NetworkingConfig: Section
575 private ArrayList netBeagleNodes = new ArrayList ();
577 [XmlArray]
578 [XmlArrayItem(ElementName="NetBeagleNodes", Type=typeof(string))]
579 public ArrayList NetBeagleNodes {
580 get { return ArrayList.ReadOnly (netBeagleNodes); }
581 set { netBeagleNodes = value; }
584 [ConfigOption (Description="List Networked Beagle Daemons to query", IsMutator=false)]
585 internal bool ListBeagleNodes (out string output, string [] args)
587 output = "Current list of Networked Beagle Daemons to query:\n";
589 foreach (string nb in netBeagleNodes)
590 output += " - " + nb + "\n";
592 return true;
595 [ConfigOption (Description="Add a Networked Beagle Daemon to query", Params=1, ParamsDescription="HostName:PortNo")]
596 internal bool AddBeagleNode (out string output, string [] args)
598 string node = args[0];
600 if (((string[])node.Split(':')).Length < 2)
601 node = args [0].Trim() + ":8888";
603 netBeagleNodes.Add(node);
604 output = "Networked Beagle Daemon \"" + node +"\" added.";
605 return true;
608 [ConfigOption (Description="Remove a configured Networked Beagle Daemon", Params=1, ParamsDescription="HostName:PortNo")]
609 internal bool DelBeagleNode (out string output, string [] args)
611 string node = args[0];
613 if (((string[])node.Split(':')).Length < 2)
614 node = args [0].Trim() + ":8888";
616 netBeagleNodes.Remove(node);
617 output = "Networked Beagle Daemon \"" + node +"\" removed.";
618 return true;
621 //#endif
623 public class Section {
624 [XmlIgnore]
625 public bool SaveNeeded = false;
628 private class ConfigOption : Attribute {
629 public string Description;
630 public int Params;
631 public string ParamsDescription;
632 public bool IsMutator = true;
635 private class ConfigSection : Attribute {
636 public string Name;
639 public class ConfigException : Exception {
640 public ConfigException (string msg) : base (msg) { }
645 //////////////////////////////////////////////////////////////////////
647 public enum ExcludeType {
648 Path,
649 Pattern,
650 MailFolder
653 public class ExcludeItem {
655 private ExcludeType type;
656 private string val;
658 [XmlAttribute]
659 public ExcludeType Type {
660 get { return type; }
661 set { type = value; }
664 private string exactMatch;
665 private string prefix;
666 private string suffix;
667 private Regex regex;
669 [XmlAttribute]
670 public string Value {
671 get { return val; }
672 set {
673 switch (type) {
674 case ExcludeType.Path:
675 case ExcludeType.MailFolder:
676 prefix = value;
677 break;
679 case ExcludeType.Pattern:
680 if (value.StartsWith ("/") && value.EndsWith ("/")) {
681 regex = new Regex (value.Substring (1, value.Length - 2));
682 break;
685 int i = value.IndexOf ('*');
686 if (i == -1) {
687 exactMatch = value;
688 } else {
689 if (i > 0)
690 prefix = value.Substring (0, i);
691 if (i < value.Length-1)
692 suffix = value.Substring (i+1);
694 break;
697 val = value;
701 public ExcludeItem () {}
703 public ExcludeItem (ExcludeType type, string value) {
704 this.Type = type;
705 this.Value = value;
708 public bool IsMatch (string param)
710 switch (Type) {
711 case ExcludeType.Path:
712 case ExcludeType.MailFolder:
713 if (prefix != null && ! param.StartsWith (prefix))
714 return false;
716 return true;
718 case ExcludeType.Pattern:
719 if (exactMatch != null)
720 return param == exactMatch;
721 if (prefix != null && ! param.StartsWith (prefix))
722 return false;
723 if (suffix != null && ! param.EndsWith (suffix))
724 return false;
725 if (regex != null && ! regex.IsMatch (param))
726 return false;
728 return true;
731 return false;
734 public override bool Equals (object obj)
736 ExcludeItem exclude = obj as ExcludeItem;
737 return (exclude != null && exclude.Type == type && exclude.Value == val);
740 public override int GetHashCode ()
742 return (this.Value.GetHashCode () ^ (int) this.Type);
747 //////////////////////////////////////////////////////////////////////
749 public class KeyBinding {
750 public string Key;
752 [XmlAttribute]
753 public bool Ctrl = false;
754 [XmlAttribute]
755 public bool Alt = false;
757 public KeyBinding () {}
758 public KeyBinding (string key) : this (key, false, false) {}
760 public KeyBinding (string key, bool ctrl, bool alt)
762 Key = key;
763 Ctrl = ctrl;
764 Alt = alt;
767 public override string ToString ()
769 string result = "";
771 if (Ctrl)
772 result += "<Ctrl>";
773 if (Alt)
774 result += "<Alt>";
776 result += Key;
778 return result;
781 public string ToReadableString ()
783 return ToString ().Replace (">", "-").Replace ("<", "");