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
.Diagnostics
;
31 using System
.Reflection
;
32 using System
.Xml
.Serialization
;
33 using System
.Text
.RegularExpressions
;
37 namespace Beagle
.Util
{
44 public static Hashtable Sections
;
46 public static IndexingConfig Indexing
= null;
47 public static DaemonConfig Daemon
= null;
48 public static SearchingConfig Searching
= null;
50 //#if ENABLE_WEBSERVICES
51 public static NetworkingConfig Networking
= null;
52 public static WebServicesConfig WebServices
= null;
54 private static string configs_dir
;
55 private static Hashtable mtimes
;
56 private static Hashtable subscriptions
;
58 private static bool watching_for_updates
;
59 private static bool update_watch_present
;
61 private static BindingFlags method_search_flags
= BindingFlags
.NonPublic
| BindingFlags
.Instance
| BindingFlags
.InvokeMethod
;
63 public delegate void ConfigUpdateHandler (Section section
);
67 Sections
= new Hashtable (3);
68 mtimes
= new Hashtable (3);
69 subscriptions
= new Hashtable (3);
71 configs_dir
= Path
.Combine (PathFinder
.StorageDir
, "config");
72 if (!Directory
.Exists (configs_dir
))
73 Directory
.CreateDirectory (configs_dir
);
78 public static void WatchForUpdates ()
80 // Make sure we don't try and watch for updates more than once
81 if (update_watch_present
)
84 if (Inotify
.Enabled
) {
85 Inotify
.Subscribe (configs_dir
, OnInotifyEvent
, Inotify
.EventType
.Create
| Inotify
.EventType
.CloseWrite
);
87 // Poll for updates every 60 secs
88 GLib
.Timeout
.Add (60000, new GLib
.TimeoutHandler (CheckForUpdates
));
91 update_watch_present
= true;
94 private static void OnInotifyEvent (Inotify
.Watch watch
, string path
, string subitem
, string srcpath
, Inotify
.EventType type
)
96 if (subitem
== "" || watching_for_updates
== false)
102 private static bool CheckForUpdates ()
104 if (watching_for_updates
)
109 public static void Subscribe (Type type
, ConfigUpdateHandler callback
)
111 if (!subscriptions
.ContainsKey (type
))
112 subscriptions
.Add (type
, new ArrayList (1));
114 ArrayList callbacks
= (ArrayList
) subscriptions
[type
];
115 callbacks
.Add (callback
);
118 private static void NotifySubscribers (Section section
)
120 Type type
= section
.GetType ();
121 ArrayList callbacks
= (ArrayList
) subscriptions
[type
];
123 if (callbacks
== null)
126 foreach (ConfigUpdateHandler callback
in callbacks
)
130 public static void Load ()
135 public static void Load (bool force
)
140 LoadFile (typeof (IndexingConfig
), Indexing
, out temp
, force
);
141 Indexing
= (IndexingConfig
) temp
;
142 NotifySubscribers (Indexing
);
144 LoadFile (typeof (DaemonConfig
), Daemon
, out temp
, force
);
145 Daemon
= (DaemonConfig
) temp
;
146 NotifySubscribers (Daemon
);
148 LoadFile (typeof (SearchingConfig
), Searching
, out temp
, force
);
149 Searching
= (SearchingConfig
) temp
;
150 NotifySubscribers (Searching
);
152 //#if ENABLE_WEBSERVICES
153 LoadFile (typeof (NetworkingConfig
), Networking
, out temp
, force
);
154 Networking
= (NetworkingConfig
) temp
;
155 NotifySubscribers (Networking
);
157 LoadFile (typeof (WebServicesConfig
), WebServices
, out temp
, force
);
158 WebServices
= (WebServicesConfig
) temp
;
159 NotifySubscribers (WebServices
);
162 watching_for_updates
= true;
165 public static void Save ()
170 public static void Save (bool force
)
172 foreach (Section section
in Sections
.Values
)
173 if (force
|| section
.SaveNeeded
)
177 private static bool LoadFile (Type type
, Section current
, out Section section
, bool force
)
180 object [] attrs
= Attribute
.GetCustomAttributes (type
, typeof (ConfigSection
));
181 if (attrs
.Length
== 0)
182 throw new ConfigException ("Could not find ConfigSection attribute on " + type
);
184 string sectionname
= ((ConfigSection
) attrs
[0]).Name
;
185 string filename
= sectionname
+ ".xml";
186 string filepath
= Path
.Combine (configs_dir
, filename
);
187 if (!File
.Exists (filepath
)) {
189 ConstructDefaultSection (type
, sectionname
, out section
);
193 if (!force
&& current
!= null && mtimes
.ContainsKey (sectionname
) &&
194 File
.GetLastWriteTimeUtc (filepath
).CompareTo ((DateTime
) mtimes
[sectionname
]) <= 0)
197 Logger
.Log
.Debug ("Loading {0} from {1}", type
, filename
);
198 FileStream fs
= null;
201 fs
= File
.Open (filepath
, FileMode
.Open
, FileAccess
.Read
, FileShare
.Read
);
202 XmlSerializer serializer
= new XmlSerializer (type
);
203 section
= (Section
) serializer
.Deserialize (fs
);
204 } catch (Exception e
) {
205 Logger
.Log
.Error ("Could not load configuration from {0}: {1}", filename
, e
.Message
);
209 ConstructDefaultSection (type
, sectionname
, out section
);
214 Sections
.Remove (sectionname
);
215 Sections
.Add (sectionname
, section
);
216 mtimes
.Remove (sectionname
);
217 mtimes
.Add (sectionname
, File
.GetLastWriteTimeUtc (filepath
));
221 private static bool SaveFile (Section section
)
223 Type type
= section
.GetType ();
224 object [] attrs
= Attribute
.GetCustomAttributes (type
, typeof (ConfigSection
));
225 if (attrs
.Length
== 0)
226 throw new ConfigException ("Could not find ConfigSection attribute on " + type
);
228 string sectionname
= ((ConfigSection
) attrs
[0]).Name
;
229 string filename
= sectionname
+ ".xml";
230 string filepath
= Path
.Combine (configs_dir
, filename
);
232 Logger
.Log
.Debug ("Saving {0} to {1}", type
, filename
);
233 FileStream fs
= null;
236 watching_for_updates
= false;
237 fs
= new FileStream (filepath
, FileMode
.Create
);
238 XmlSerializer serializer
= new XmlSerializer (type
);
239 XmlFu
.SerializeUtf8 (serializer
, fs
, section
);
240 } catch (Exception e
) {
243 Logger
.Log
.Error ("Could not save configuration to {0}: {1}", filename
, e
);
244 watching_for_updates
= true;
249 mtimes
.Remove (sectionname
);
250 mtimes
.Add (sectionname
, File
.GetLastWriteTimeUtc (filepath
));
251 watching_for_updates
= true;
255 private static void ConstructDefaultSection (Type type
, string sectionname
, out Section section
)
257 ConstructorInfo ctor
= type
.GetConstructor (Type
.EmptyTypes
);
258 section
= (Section
) ctor
.Invoke (null);
259 Sections
.Remove (sectionname
);
260 Sections
.Add (sectionname
, section
);
263 // Lists all config file options in a hash table where key is option name,
264 // and value is description.
265 public static Hashtable
GetOptions (Section section
)
267 Hashtable options
= new Hashtable ();
268 MemberInfo
[] members
= section
.GetType ().GetMembers (method_search_flags
);
270 // Find all of the methods ("options") inside the specified section
271 // object which have the ConfigOption attribute.
272 foreach (MemberInfo member
in members
) {
273 object [] attrs
= member
.GetCustomAttributes (typeof (ConfigOption
), false);
274 if (attrs
.Length
> 0)
275 options
.Add (member
.Name
, ((ConfigOption
) attrs
[0]).Description
);
281 public static bool InvokeOption (Section section
, string option
, string [] args
, out string output
)
283 MethodInfo method
= section
.GetType ().GetMethod (option
, method_search_flags
);
284 if (method
== null) {
285 string msg
= String
.Format ("No such method '{0}' for section '{1}'", option
, section
);
286 throw new ConfigException(msg
);
288 object [] attrs
= method
.GetCustomAttributes (typeof (ConfigOption
), false);
289 if (attrs
.Length
== 0) {
290 string msg
= String
.Format ("Method '{0}' is not a configurable option", option
);
291 throw new ConfigException (msg
);
294 // Check the required number of parameters have been provided
295 ConfigOption attr
= (ConfigOption
) attrs
[0];
296 if (attr
.Params
> 0 && args
.Length
< attr
.Params
) {
297 string msg
= String
.Format ("Option '{0}' requires {1} parameter(s): {2}", option
, attr
.Params
, attr
.ParamsDescription
);
298 throw new ConfigException (msg
);
301 object [] methodparams
= { null, args }
;
302 bool result
= (bool) method
.Invoke (section
, methodparams
);
303 output
= (string) methodparams
[0];
305 // Mark the section as save-needed if we just changed something
306 if (result
&& attr
.IsMutator
)
307 section
.SaveNeeded
= true;
312 [ConfigSection (Name
="searching")]
313 public class SearchingConfig
: Section
{
315 private bool autostart
= true;
316 public bool Autostart
{
317 get { return autostart; }
318 set { autostart = value; }
321 private KeyBinding show_search_window_binding
= new KeyBinding ("F12");
322 public KeyBinding ShowSearchWindowBinding
{
323 get { return show_search_window_binding; }
324 set { show_search_window_binding = value; }
327 private int max_displayed
= 5;
328 public int MaxDisplayed
{
329 get { return max_displayed; }
334 max_displayed
= value;
338 // Best window position and dimension
339 // stored as percentage of screen co-ordinates
340 // to deal with change of resolution problem - hints from tberman
342 private float best_pos_x
= 0;
343 public float BestPosX
{
344 get { return best_pos_x; }
345 set { best_pos_x = value; }
348 private float best_pos_y
= 0;
349 public float BestPosY
{
350 get { return best_pos_y; }
351 set { best_pos_y = value; }
354 // dont explicitly set height on first run
355 private float best_width
= 0;
356 public float BestWidth
{
357 get { return best_width; }
358 set { best_width = value; }
361 private float best_height
= 0; // -ditto-
362 public float BestHeight
{
363 get { return best_height; }
364 set { best_height = value; }
367 // FIXME Change names when Holmes is released under new name
368 // Holmes window position and dimension
370 private float holmes_pos_x
= 0;
371 public float HolmesPosX
{
372 get { return holmes_pos_x; }
373 set { holmes_pos_x = value; }
376 private float holmes_pos_y
= 0;
377 public float HolmesPosY
{
378 get { return holmes_pos_y; }
379 set { holmes_pos_y = value; }
382 private float holmes_width
= 0;
383 public float HolmesWidth
{
384 get { return holmes_width; }
385 set { holmes_width = value; }
388 private float holmes_height
= 0;
389 public float HolmesHeight
{
390 get { return holmes_height; }
391 set { holmes_height = value; }
394 // ah!We want a Queue but Queue doesnt serialize *easily*
395 private ArrayList search_history
= new ArrayList ();
396 public ArrayList SearchHistory
{
397 get { return search_history; }
398 set { search_history = value; }
403 [ConfigSection (Name
="daemon")]
404 public class DaemonConfig
: Section
{
405 private ArrayList static_queryables
= new ArrayList ();
406 public ArrayList StaticQueryables
{
407 get { return static_queryables; }
408 set { static_queryables = value; }
411 // By default, every backend is allowed.
412 // Only maintain a list of denied backends.
413 private ArrayList denied_backends
= new ArrayList ();
414 public ArrayList DeniedBackends
{
415 get { return denied_backends; }
416 set { denied_backends = value; }
419 private bool allow_static_backend
= false; // by default, false
420 public bool AllowStaticBackend
{
421 get { return allow_static_backend; }
422 // Don't really want to expose this, but serialization requires it
423 set { allow_static_backend = value; }
426 private bool index_synchronization
= true;
427 public bool IndexSynchronization
{
428 get { return index_synchronization; }
429 // Don't really want to expose this, but serialization requires it
430 set { index_synchronization = value; }
433 [ConfigOption (Description
="Enable a backend", Params
=1, ParamsDescription
="Name of the backend to enable")]
434 internal bool AllowBackend (out string output
, string [] args
)
436 denied_backends
.Remove (args
[0]);
437 output
= "Backend allowed (need to restart beagled for changes to take effect).";
441 [ConfigOption (Description
="Disable a backend", Params
=1, ParamsDescription
="Name of the backend to disable")]
442 internal bool DenyBackend (out string output
, string [] args
)
444 denied_backends
.Add (args
[0]);
445 output
= "Backend disabled (need to restart beagled for changes to take effect).";
449 private bool allow_root
= false;
450 public bool AllowRoot
{
451 get { return allow_root; }
452 set { allow_root = value; }
455 [ConfigOption (Description
="Add a static queryable", Params
=1, ParamsDescription
="Index path")]
456 internal bool AddStaticQueryable (out string output
, string [] args
)
458 static_queryables
.Add (args
[0]);
459 output
= "Static queryable added.";
463 [ConfigOption (Description
="Remove a static queryable", Params
=1, ParamsDescription
="Index path")]
464 internal bool DelStaticQueryable (out string output
, string [] args
)
466 static_queryables
.Remove (args
[0]);
467 output
= "Static queryable removed.";
471 [ConfigOption (Description
="List user-specified static queryables", IsMutator
=false)]
472 internal bool ListStaticQueryables (out string output
, string [] args
)
474 output
= "User-specified static queryables:\n";
475 foreach (string index_path
in static_queryables
)
476 output
+= String
.Format (" - {0}\n", index_path
);
480 [ConfigOption (Description
="Toggles whether static indexes will be enabled")]
481 internal bool ToggleAllowStaticBackend (out string output
, string [] args
)
483 allow_static_backend
= !allow_static_backend
;
484 output
= "Static indexes are " + ((allow_static_backend
) ? "enabled" : "disabled") + " (need to restart beagled for changes to take effect).";
488 [ConfigOption (Description
="Toggles whether your indexes will be synchronized locally if your home directory is on a network device (eg. NFS/Samba)")]
489 internal bool ToggleIndexSynchronization (out string output
, string [] args
)
491 index_synchronization
= !index_synchronization
;
492 output
= "Index Synchronization is " + ((index_synchronization
) ? "enabled" : "disabled") + ".";
496 [ConfigOption (Description
="Toggles whether Beagle can be run as root")]
497 internal bool ToggleAllowRoot (out string output
, string [] args
)
499 allow_root
= ! allow_root
;
501 output
= "Beagle is now permitted to run as root";
503 output
= "Beagle is no longer permitted to run as root";
508 [ConfigSection (Name
="indexing")]
509 public class IndexingConfig
: Section
511 private ArrayList roots
= new ArrayList ();
513 [XmlArrayItem(ElementName
="Root", Type
=typeof(string))]
514 public ArrayList Roots
{
515 get { return roots; }
516 set { roots = value; }
519 private bool index_home_dir
= true;
520 public bool IndexHomeDir
{
521 get { return index_home_dir; }
522 set { index_home_dir = value; }
525 private ArrayList excludes
= new ArrayList ();
527 [XmlArrayItem (ElementName
="ExcludeItem", Type
=typeof(ExcludeItem
))]
528 public ArrayList Excludes
{
529 get { return excludes; }
530 set { excludes = value; }
533 [ConfigOption (Description
="List the indexing roots", IsMutator
=false)]
534 internal bool ListRoots (out string output
, string [] args
)
536 output
= "Current roots:\n";
537 if (this.index_home_dir
== true)
538 output
+= " - Your home directory\n";
539 foreach (string root
in roots
)
540 output
+= " - " + root
+ "\n";
545 [ConfigOption (Description
="Toggles whether your home directory is to be indexed as a root")]
546 internal bool IndexHome (out string output
, string [] args
)
549 output
= "Your home directory will not be indexed.";
551 output
= "Your home directory will be indexed.";
552 index_home_dir
= !index_home_dir
;
556 [ConfigOption (Description
="Add a root path to be indexed", Params
=1, ParamsDescription
="A path")]
557 internal bool AddRoot (out string output
, string [] args
)
559 roots
.Add (args
[0]);
560 output
= "Root added.";
564 [ConfigOption (Description
="Remove an indexing root", Params
=1, ParamsDescription
="A path")]
565 internal bool DelRoot (out string output
, string [] args
)
567 roots
.Remove (args
[0]);
568 output
= "Root removed.";
572 [ConfigOption (Description
="List user-specified resources to be excluded from indexing", IsMutator
=false)]
573 internal bool ListExcludes (out string output
, string [] args
)
575 output
= "User-specified resources to be excluded from indexing:\n";
576 foreach (ExcludeItem exclude_item
in excludes
)
577 output
+= String
.Format (" - [{0}] {1}\n", exclude_item
.Type
.ToString (), exclude_item
.Value
);
581 [ConfigOption (Description
="Add a resource to exclude from indexing", Params
=2, ParamsDescription
="A type [path/pattern/mailfolder], a path/pattern/name")]
582 internal bool AddExclude (out string output
, string [] args
)
586 type
= (ExcludeType
) Enum
.Parse (typeof (ExcludeType
), args
[0], true);
587 } catch (Exception e
) {
588 output
= String
.Format("Invalid type '{0}'. Valid types: Path, Pattern, MailFolder", args
[0]);
592 excludes
.Add (new ExcludeItem (type
, args
[1]));
593 output
= "Exclude added.";
597 [ConfigOption (Description
="Remove an excluded resource", Params
=2, ParamsDescription
="A type [path/pattern/mailfolder], a path/pattern/name")]
598 internal bool DelExclude (out string output
, string [] args
)
602 type
= (ExcludeType
) Enum
.Parse (typeof (ExcludeType
), args
[0], true);
603 } catch (Exception e
) {
604 output
= String
.Format("Invalid type '{0}'. Valid types: Path, Pattern, MailFolder", args
[0]);
608 foreach (ExcludeItem item
in excludes
) {
609 if (item
.Type
!= type
|| item
.Value
!= args
[1])
611 excludes
.Remove (item
);
612 output
= "Exclude removed.";
616 output
= "Could not find requested exclude to remove.";
622 //#if ENABLE_WEBSERVICES
623 [ConfigSection (Name
="webservices")]
624 public class WebServicesConfig
: Section
626 private ArrayList publicFolders
= new ArrayList ();
628 [XmlArrayItem(ElementName
="PublicFolders", Type
=typeof(string))]
629 public ArrayList PublicFolders
{
630 get { return publicFolders; }
631 set { publicFolders = value; }
634 private bool allowGlobalAccess
= true;
635 public bool AllowGlobalAccess
{
636 get { return allowGlobalAccess; }
637 set { allowGlobalAccess = value; }
640 [ConfigOption (Description
="List the public folders", IsMutator
=false)]
641 internal bool ListPublicFolders(out string output
, string [] args
)
643 output
= "Current list of public folders:\n";
645 foreach (string pf
in publicFolders
)
646 output
+= " - " + pf
+ "\n";
651 [ConfigOption (Description
="Check current configuration of global access to Beagle web-services", IsMutator
=false)]
652 internal bool CheckGlobalAccess(out string output
, string [] args
)
654 if (allowGlobalAccess
)
655 output
= "Global Access to Beagle WebServices is currently ENABLED.";
657 output
= "Global Access to Beagle WebServices is currently DISABLED.";
662 [ConfigOption (Description
="Enable/Disable global access to Beagle web-services")]
663 internal bool SwitchGlobalAccess (out string output
, string [] args
)
665 allowGlobalAccess
= !allowGlobalAccess
;
667 if (allowGlobalAccess
)
668 output
= "Global Access to Beagle WebServices now ENABLED.";
670 output
= "Global Access to Beagle WebServices now DISABLED.";
675 [ConfigOption (Description
="Add public web-service access to a folder", Params
=1, ParamsDescription
="A path")]
676 internal bool AddPublicFolder (out string output
, string [] args
)
678 publicFolders
.Add (args
[0]);
679 output
= "PublicFolder " + args
[0] + " added.";
683 [ConfigOption (Description
="Remove public web-service access to a folder", Params
=1, ParamsDescription
="A path")]
684 internal bool DelPublicFolder (out string output
, string [] args
)
686 publicFolders
.Remove (args
[0]);
687 output
= "PublicFolder " + args
[0] + " removed.";
693 [ConfigSection (Name
="networking")]
694 public class NetworkingConfig
: Section
696 private ArrayList netBeagleNodes
= new ArrayList ();
699 [XmlArrayItem(ElementName
="NetBeagleNodes", Type
=typeof(string))]
700 public ArrayList NetBeagleNodes
{
701 get { return netBeagleNodes; }
702 set { netBeagleNodes = value; }
705 [ConfigOption (Description
="List Networked Beagle Daemons to query", IsMutator
=false)]
706 internal bool ListBeagleNodes (out string output
, string [] args
)
708 output
= "Current list of Networked Beagle Daemons to query:\n";
710 foreach (string nb
in netBeagleNodes
)
711 output
+= " - " + nb
+ "\n";
716 [ConfigOption (Description
="Add a Networked Beagle Daemon to query", Params
=1, ParamsDescription
="HostName:PortNo")]
717 internal bool AddBeagleNode (out string output
, string [] args
)
719 string node
= args
[0];
721 if (((string[])node
.Split(':')).Length
< 2)
722 node
= args
[0].Trim() + ":8888";
724 netBeagleNodes
.Add(node
);
725 output
= "Networked Beagle Daemon \"" + node
+"\" added.";
729 [ConfigOption (Description
="Remove a configured Networked Beagle Daemon", Params
=1, ParamsDescription
="HostName:PortNo")]
730 internal bool DelBeagleNode (out string output
, string [] args
)
732 string node
= args
[0];
734 if (((string[])node
.Split(':')).Length
< 2)
735 node
= args
[0].Trim() + ":8888";
737 netBeagleNodes
.Remove(node
);
738 output
= "Networked Beagle Daemon \"" + node
+"\" removed.";
744 public class Section
{
746 public bool SaveNeeded
= false;
749 private class ConfigOption
: Attribute
{
750 public string Description
;
752 public string ParamsDescription
;
753 public bool IsMutator
= true;
756 private class ConfigSection
: Attribute
{
760 public class ConfigException
: Exception
{
761 public ConfigException (string msg
) : base (msg
) { }
766 //////////////////////////////////////////////////////////////////////
768 public enum ExcludeType
{
774 public class ExcludeItem
{
776 private ExcludeType type
;
780 public ExcludeType Type
{
782 set { type = value; }
785 private string exactMatch
;
786 private string prefix
;
787 private string suffix
;
791 public string Value
{
795 case ExcludeType
.Path
:
796 case ExcludeType
.MailFolder
:
800 case ExcludeType
.Pattern
:
801 if (value.StartsWith ("/") && value.EndsWith ("/")) {
802 regex
= new Regex (value.Substring (1, value.Length
- 2));
806 int i
= value.IndexOf ('*');
811 prefix
= value.Substring (0, i
);
812 if (i
< value.Length
-1)
813 suffix
= value.Substring (i
+1);
822 public ExcludeItem () {}
824 public ExcludeItem (ExcludeType type
, string value) {
829 public bool IsMatch (string param
)
832 case ExcludeType
.Path
:
833 case ExcludeType
.MailFolder
:
834 if (prefix
!= null && ! param
.StartsWith (prefix
))
839 case ExcludeType
.Pattern
:
840 if (exactMatch
!= null)
841 return param
== exactMatch
;
842 if (prefix
!= null && ! param
.StartsWith (prefix
))
844 if (suffix
!= null && ! param
.EndsWith (suffix
))
846 if (regex
!= null && ! regex
.IsMatch (param
))
855 public override bool Equals (object obj
)
857 ExcludeItem exclude
= obj
as ExcludeItem
;
858 return (exclude
!= null && exclude
.Type
== type
&& exclude
.Value
== val
);
861 public override int GetHashCode ()
863 return (this.Value
.GetHashCode () ^
(int) this.Type
);
868 //////////////////////////////////////////////////////////////////////
870 public class KeyBinding
{
874 public bool Ctrl
= false;
876 public bool Alt
= false;
878 public KeyBinding () {}
879 public KeyBinding (string key
) : this (key
, false, false) {}
881 public KeyBinding (string key
, bool ctrl
, bool alt
)
888 public override string ToString ()
902 public string ToReadableString ()
904 return ToString ().Replace (">", "-").Replace ("<", "");