4 // Copyright (C) 2005 Novell, Inc.
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
28 using System
.Collections
;
30 using System
.Reflection
;
31 using System
.Xml
.Serialization
;
32 using System
.Text
.RegularExpressions
;
36 namespace Beagle
.Util
{
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;
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
);
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
);
77 public static void WatchForUpdates ()
79 // Make sure we don't try and watch for updates more than once
80 if (update_watch_present
)
83 if (Inotify
.Enabled
) {
84 Inotify
.Subscribe (configs_dir
, OnInotifyEvent
, Inotify
.EventType
.Create
| Inotify
.EventType
.CloseWrite
);
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)
101 private static bool CheckForUpdates ()
103 if (watching_for_updates
)
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)
125 foreach (ConfigUpdateHandler callback
in callbacks
)
129 public static void Load ()
134 public static void Load (bool force
)
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
);
161 watching_for_updates
= true;
164 public static void Save ()
169 public static void Save (bool force
)
171 foreach (Section section
in Sections
.Values
)
172 if (force
|| section
.SaveNeeded
)
176 private static bool LoadFile (Type type
, Section current
, out Section section
, bool force
)
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
)) {
188 ConstructDefaultSection (type
, sectionname
, out section
);
192 if (!force
&& current
!= null && mtimes
.ContainsKey (sectionname
) &&
193 File
.GetLastWriteTimeUtc (filepath
).CompareTo ((DateTime
) mtimes
[sectionname
]) <= 0)
196 Logger
.Log
.Debug ("Loading {0} from {1}", type
, filename
);
197 FileStream fs
= null;
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
);
208 ConstructDefaultSection (type
, sectionname
, out section
);
213 Sections
.Remove (sectionname
);
214 Sections
.Add (sectionname
, section
);
215 mtimes
.Remove (sectionname
);
216 mtimes
.Add (sectionname
, File
.GetLastWriteTimeUtc (filepath
));
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;
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
) {
242 Logger
.Log
.Error ("Could not save configuration to {0}: {1}", filename
, e
);
243 watching_for_updates
= true;
248 mtimes
.Remove (sectionname
);
249 mtimes
.Add (sectionname
, File
.GetLastWriteTimeUtc (filepath
));
250 watching_for_updates
= 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
);
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;
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.";
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.";
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
);
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") + ".";
388 [ConfigSection (Name
="indexing")]
389 public class IndexingConfig
: Section
391 private ArrayList roots
= new ArrayList ();
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 ();
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";
425 [ConfigOption (Description
="Toggles whether your home directory is to be indexed as a root")]
426 internal bool IndexHome (out string output
, string [] args
)
429 output
= "Your home directory will not be indexed.";
431 output
= "Your home directory will be indexed.";
432 index_home_dir
= !index_home_dir
;
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.";
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.";
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
);
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
)
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]);
472 excludes
.Add (new ExcludeItem (type
, args
[1]));
473 output
= "Exclude added.";
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
)
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]);
488 foreach (ExcludeItem item
in excludes
) {
489 if (item
.Type
!= type
|| item
.Value
!= args
[1])
491 excludes
.Remove (item
);
492 output
= "Exclude removed.";
496 output
= "Could not find requested exclude to remove.";
502 //#if ENABLE_WEBSERVICES
503 [ConfigSection (Name
="webservices")]
504 public class WebServicesConfig
: Section
506 private ArrayList publicFolders
= new ArrayList ();
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";
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.";
537 output
= "Global Access to Beagle WebServices is currently DISABLED.";
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.";
550 output
= "Global Access to Beagle WebServices now DISABLED.";
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.";
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.";
572 [ConfigSection (Name
="networking")]
573 public class NetworkingConfig
: Section
575 private ArrayList netBeagleNodes
= new ArrayList ();
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";
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.";
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.";
623 public class Section
{
625 public bool SaveNeeded
= false;
628 private class ConfigOption
: Attribute
{
629 public string Description
;
631 public string ParamsDescription
;
632 public bool IsMutator
= true;
635 private class ConfigSection
: Attribute
{
639 public class ConfigException
: Exception
{
640 public ConfigException (string msg
) : base (msg
) { }
645 //////////////////////////////////////////////////////////////////////
647 public enum ExcludeType
{
653 public class ExcludeItem
{
655 private ExcludeType type
;
659 public ExcludeType Type
{
661 set { type = value; }
664 private string exactMatch
;
665 private string prefix
;
666 private string suffix
;
670 public string Value
{
674 case ExcludeType
.Path
:
675 case ExcludeType
.MailFolder
:
679 case ExcludeType
.Pattern
:
680 if (value.StartsWith ("/") && value.EndsWith ("/")) {
681 regex
= new Regex (value.Substring (1, value.Length
- 2));
685 int i
= value.IndexOf ('*');
690 prefix
= value.Substring (0, i
);
691 if (i
< value.Length
-1)
692 suffix
= value.Substring (i
+1);
701 public ExcludeItem () {}
703 public ExcludeItem (ExcludeType type
, string value) {
708 public bool IsMatch (string param
)
711 case ExcludeType
.Path
:
712 case ExcludeType
.MailFolder
:
713 if (prefix
!= null && ! param
.StartsWith (prefix
))
718 case ExcludeType
.Pattern
:
719 if (exactMatch
!= null)
720 return param
== exactMatch
;
721 if (prefix
!= null && ! param
.StartsWith (prefix
))
723 if (suffix
!= null && ! param
.EndsWith (suffix
))
725 if (regex
!= null && ! regex
.IsMatch (param
))
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
{
753 public bool Ctrl
= false;
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
)
767 public override string ToString ()
781 public string ToReadableString ()
783 return ToString ().Replace (">", "-").Replace ("<", "");