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 private static string configs_dir
;
51 private static Hashtable mtimes
;
52 private static Hashtable subscriptions
;
54 private static bool watching_for_updates
;
55 private static bool update_watch_present
;
57 private static BindingFlags method_search_flags
= BindingFlags
.NonPublic
| BindingFlags
.Instance
| BindingFlags
.InvokeMethod
;
59 public delegate void ConfigUpdateHandler (Section section
);
63 Sections
= new Hashtable (3);
64 mtimes
= new Hashtable (3);
65 subscriptions
= new Hashtable (3);
67 configs_dir
= Path
.Combine (PathFinder
.StorageDir
, "config");
68 if (!Directory
.Exists (configs_dir
))
69 Directory
.CreateDirectory (configs_dir
);
74 public static void WatchForUpdates ()
76 // Make sure we don't try and watch for updates more than once
77 if (update_watch_present
)
80 if (Inotify
.Enabled
) {
81 Inotify
.Subscribe (configs_dir
, OnInotifyEvent
, Inotify
.EventType
.Create
| Inotify
.EventType
.CloseWrite
);
83 // Poll for updates every 60 secs
84 GLib
.Timeout
.Add (60000, new GLib
.TimeoutHandler (CheckForUpdates
));
87 update_watch_present
= true;
90 private static void OnInotifyEvent (Inotify
.Watch watch
, string path
, string subitem
, string srcpath
, Inotify
.EventType type
)
92 if (subitem
== "" || watching_for_updates
== false)
98 private static bool CheckForUpdates ()
100 if (watching_for_updates
)
105 public static void Subscribe (Type type
, ConfigUpdateHandler callback
)
107 if (!subscriptions
.ContainsKey (type
))
108 subscriptions
.Add (type
, new ArrayList (1));
110 ArrayList callbacks
= (ArrayList
) subscriptions
[type
];
111 callbacks
.Add (callback
);
114 private static void NotifySubscribers (Section section
)
116 Type type
= section
.GetType ();
117 ArrayList callbacks
= (ArrayList
) subscriptions
[type
];
119 if (callbacks
== null)
122 foreach (ConfigUpdateHandler callback
in callbacks
)
126 public static void Load ()
131 public static void Load (bool force
)
136 LoadFile (typeof (IndexingConfig
), Indexing
, out temp
, force
);
137 Indexing
= (IndexingConfig
) temp
;
138 NotifySubscribers (Indexing
);
140 LoadFile (typeof (DaemonConfig
), Daemon
, out temp
, force
);
141 Daemon
= (DaemonConfig
) temp
;
142 NotifySubscribers (Daemon
);
144 LoadFile (typeof (SearchingConfig
), Searching
, out temp
, force
);
145 Searching
= (SearchingConfig
) temp
;
146 NotifySubscribers (Searching
);
148 watching_for_updates
= true;
151 public static void Save ()
156 public static void Save (bool force
)
158 foreach (Section section
in Sections
.Values
)
159 if (force
|| section
.SaveNeeded
)
163 private static bool LoadFile (Type type
, Section current
, out Section section
, bool force
)
166 object [] attrs
= Attribute
.GetCustomAttributes (type
, typeof (ConfigSection
));
167 if (attrs
.Length
== 0)
168 throw new ConfigException ("Could not find ConfigSection attribute on " + type
);
170 string sectionname
= ((ConfigSection
) attrs
[0]).Name
;
171 string filename
= sectionname
+ ".xml";
172 string filepath
= Path
.Combine (configs_dir
, filename
);
173 if (!File
.Exists (filepath
)) {
175 ConstructDefaultSection (type
, sectionname
, out section
);
179 if (!force
&& current
!= null && mtimes
.ContainsKey (sectionname
) &&
180 File
.GetLastWriteTimeUtc (filepath
).CompareTo ((DateTime
) mtimes
[sectionname
]) <= 0)
183 Logger
.Log
.Debug ("Loading {0} from {1}", type
, filename
);
184 FileStream fs
= null;
187 fs
= File
.Open (filepath
, FileMode
.Open
, FileAccess
.Read
, FileShare
.Read
);
188 XmlSerializer serializer
= new XmlSerializer (type
);
189 section
= (Section
) serializer
.Deserialize (fs
);
190 } catch (Exception e
) {
191 Logger
.Log
.Error (e
, "Could not load configuration from {0}:", filename
);
195 ConstructDefaultSection (type
, sectionname
, out section
);
200 Sections
.Remove (sectionname
);
201 Sections
.Add (sectionname
, section
);
202 mtimes
.Remove (sectionname
);
203 mtimes
.Add (sectionname
, File
.GetLastWriteTimeUtc (filepath
));
207 private static bool SaveFile (Section section
)
209 Type type
= section
.GetType ();
210 object [] attrs
= Attribute
.GetCustomAttributes (type
, typeof (ConfigSection
));
211 if (attrs
.Length
== 0)
212 throw new ConfigException ("Could not find ConfigSection attribute on " + type
);
214 string sectionname
= ((ConfigSection
) attrs
[0]).Name
;
215 string filename
= sectionname
+ ".xml";
216 string filepath
= Path
.Combine (configs_dir
, filename
);
218 Logger
.Log
.Debug ("Saving {0} to {1}", type
, filename
);
219 FileStream fs
= null;
222 watching_for_updates
= false;
223 fs
= new FileStream (filepath
, FileMode
.Create
);
224 XmlSerializer serializer
= new XmlSerializer (type
);
225 XmlFu
.SerializeUtf8 (serializer
, fs
, section
);
226 } catch (Exception e
) {
229 Logger
.Log
.Error ("Could not save configuration to {0}: {1}", filename
, e
);
230 watching_for_updates
= true;
235 mtimes
.Remove (sectionname
);
236 mtimes
.Add (sectionname
, File
.GetLastWriteTimeUtc (filepath
));
237 watching_for_updates
= true;
241 private static void ConstructDefaultSection (Type type
, string sectionname
, out Section section
)
243 ConstructorInfo ctor
= type
.GetConstructor (Type
.EmptyTypes
);
244 section
= (Section
) ctor
.Invoke (null);
245 Sections
.Remove (sectionname
);
246 Sections
.Add (sectionname
, section
);
249 // Lists all config file options in a hash table where key is option name,
250 // and value is description.
251 public static Hashtable
GetOptions (Section section
)
253 Hashtable options
= new Hashtable ();
254 MemberInfo
[] members
= section
.GetType ().GetMembers (method_search_flags
);
256 // Find all of the methods ("options") inside the specified section
257 // object which have the ConfigOption attribute.
258 foreach (MemberInfo member
in members
) {
259 object [] attrs
= member
.GetCustomAttributes (typeof (ConfigOption
), false);
260 if (attrs
.Length
> 0)
261 options
.Add (member
.Name
, ((ConfigOption
) attrs
[0]).Description
);
267 public static bool InvokeOption (Section section
, string option
, string [] args
, out string output
)
269 MethodInfo method
= section
.GetType ().GetMethod (option
, method_search_flags
);
270 if (method
== null) {
271 string msg
= String
.Format ("No such method '{0}' for section '{1}'", option
, section
);
272 throw new ConfigException(msg
);
274 object [] attrs
= method
.GetCustomAttributes (typeof (ConfigOption
), false);
275 if (attrs
.Length
== 0) {
276 string msg
= String
.Format ("Method '{0}' is not a configurable option", option
);
277 throw new ConfigException (msg
);
280 // Check the required number of parameters have been provided
281 ConfigOption attr
= (ConfigOption
) attrs
[0];
282 if (attr
.Params
> 0 && args
.Length
< attr
.Params
) {
283 string msg
= String
.Format ("Option '{0}' requires {1} parameter(s): {2}", option
, attr
.Params
, attr
.ParamsDescription
);
284 throw new ConfigException (msg
);
287 object [] methodparams
= { null, args }
;
288 bool result
= (bool) method
.Invoke (section
, methodparams
);
289 output
= (string) methodparams
[0];
291 // Mark the section as save-needed if we just changed something
292 if (result
&& attr
.IsMutator
)
293 section
.SaveNeeded
= true;
298 [ConfigSection (Name
="searching")]
299 public class SearchingConfig
: Section
{
301 private bool autostart
= true;
302 public bool Autostart
{
303 get { return autostart; }
304 set { autostart = value; }
307 private KeyBinding show_search_window_binding
= new KeyBinding ("F12");
308 public KeyBinding ShowSearchWindowBinding
{
309 get { return show_search_window_binding; }
310 set { show_search_window_binding = value; }
313 private int max_displayed
= 5;
314 public int MaxDisplayed
{
315 get { return max_displayed; }
320 max_displayed
= value;
324 // BeagleSearch window position and dimension
325 // stored as percentage of screen co-ordinates
326 // to deal with change of resolution problem - hints from tberman
328 private float beagle_search_pos_x
= 0;
329 public float BeaglePosX
{
330 get { return beagle_search_pos_x; }
331 set { beagle_search_pos_x = value; }
334 private float beagle_search_pos_y
= 0;
335 public float BeaglePosY
{
336 get { return beagle_search_pos_y; }
337 set { beagle_search_pos_y = value; }
340 private float beagle_search_width
= 0;
341 public float BeagleSearchWidth
{
342 get { return beagle_search_width; }
343 set { beagle_search_width = value; }
346 private float beagle_search_height
= 0;
347 public float BeagleSearchHeight
{
348 get { return beagle_search_height; }
349 set { beagle_search_height = value; }
352 // ah!We want a Queue but Queue doesnt serialize *easily*
353 private ArrayList search_history
= new ArrayList ();
354 public ArrayList SearchHistory
{
355 get { return search_history; }
356 set { search_history = value; }
361 [ConfigSection (Name
="daemon")]
362 public class DaemonConfig
: Section
{
363 private ArrayList static_queryables
= new ArrayList ();
364 public ArrayList StaticQueryables
{
365 get { return static_queryables; }
366 set { static_queryables = value; }
369 // By default, every backend is allowed.
370 // Only maintain a list of denied backends.
371 private ArrayList denied_backends
= new ArrayList ();
372 public ArrayList DeniedBackends
{
373 get { return denied_backends; }
374 set { denied_backends = value; }
377 private bool allow_static_backend
= false; // by default, false
378 public bool AllowStaticBackend
{
379 get { return allow_static_backend; }
380 // Don't really want to expose this, but serialization requires it
381 set { allow_static_backend = value; }
384 private bool index_synchronization
= true;
385 public bool IndexSynchronization
{
386 get { return index_synchronization; }
387 // Don't really want to expose this, but serialization requires it
388 set { index_synchronization = value; }
391 [ConfigOption (Description
="Enable a backend", Params
=1, ParamsDescription
="Name of the backend to enable")]
392 internal bool AllowBackend (out string output
, string [] args
)
394 denied_backends
.Remove (args
[0]);
395 output
= "Backend allowed (need to restart beagled for changes to take effect).";
399 [ConfigOption (Description
="Disable a backend", Params
=1, ParamsDescription
="Name of the backend to disable")]
400 internal bool DenyBackend (out string output
, string [] args
)
402 denied_backends
.Add (args
[0]);
403 output
= "Backend disabled (need to restart beagled for changes to take effect).";
407 private bool allow_root
= false;
408 public bool AllowRoot
{
409 get { return allow_root; }
410 set { allow_root = value; }
413 [ConfigOption (Description
="Add a static queryable", Params
=1, ParamsDescription
="Index path")]
414 internal bool AddStaticQueryable (out string output
, string [] args
)
416 static_queryables
.Add (args
[0]);
417 output
= "Static queryable added.";
421 [ConfigOption (Description
="Remove a static queryable", Params
=1, ParamsDescription
="Index path")]
422 internal bool DelStaticQueryable (out string output
, string [] args
)
424 static_queryables
.Remove (args
[0]);
425 output
= "Static queryable removed.";
429 [ConfigOption (Description
="List user-specified static queryables", IsMutator
=false)]
430 internal bool ListStaticQueryables (out string output
, string [] args
)
432 output
= "User-specified static queryables:\n";
433 foreach (string index_path
in static_queryables
)
434 output
+= String
.Format (" - {0}\n", index_path
);
438 [ConfigOption (Description
="Toggles whether static indexes will be enabled")]
439 internal bool ToggleAllowStaticBackend (out string output
, string [] args
)
441 allow_static_backend
= !allow_static_backend
;
442 output
= "Static indexes are " + ((allow_static_backend
) ? "enabled" : "disabled") + " (need to restart beagled for changes to take effect).";
446 [ConfigOption (Description
="Toggles whether your indexes will be synchronized locally if your home directory is on a network device (eg. NFS/Samba)")]
447 internal bool ToggleIndexSynchronization (out string output
, string [] args
)
449 index_synchronization
= !index_synchronization
;
450 output
= "Index Synchronization is " + ((index_synchronization
) ? "enabled" : "disabled") + ".";
454 [ConfigOption (Description
="Toggles whether Beagle can be run as root")]
455 internal bool ToggleAllowRoot (out string output
, string [] args
)
457 allow_root
= ! allow_root
;
459 output
= "Beagle is now permitted to run as root";
461 output
= "Beagle is no longer permitted to run as root";
466 [ConfigSection (Name
="indexing")]
467 public class IndexingConfig
: Section
469 private ArrayList roots
= new ArrayList ();
471 [XmlArrayItem(ElementName
="Root", Type
=typeof(string))]
472 public ArrayList Roots
{
473 get { return roots; }
474 set { roots = value; }
477 private bool index_home_dir
= true;
478 public bool IndexHomeDir
{
479 get { return index_home_dir; }
480 set { index_home_dir = value; }
483 private bool index_on_battery
= true;
484 public bool IndexOnBattery
{
485 get { return index_on_battery; }
486 set { index_on_battery = value; }
489 private ArrayList excludes
= new ArrayList ();
491 [XmlArrayItem (ElementName
="ExcludeItem", Type
=typeof(ExcludeItem
))]
492 public ArrayList Excludes
{
493 get { return excludes; }
494 set { excludes = value; }
497 [ConfigOption (Description
="List the indexing roots", IsMutator
=false)]
498 internal bool ListRoots (out string output
, string [] args
)
500 output
= "Current roots:\n";
501 if (this.index_home_dir
== true)
502 output
+= " - Your home directory\n";
503 foreach (string root
in roots
)
504 output
+= " - " + root
+ "\n";
509 [ConfigOption (Description
="Toggles whether your home directory is to be indexed as a root")]
510 internal bool IndexHome (out string output
, string [] args
)
513 output
= "Your home directory will not be indexed.";
515 output
= "Your home directory will be indexed.";
516 index_home_dir
= !index_home_dir
;
520 [ConfigOption (Description
="Toggles whether any data should be indexed if the system is on battery")]
521 internal bool IndexWhileOnBattery (out string output
, string [] args
)
523 if (index_on_battery
)
524 output
= "Data will not be indexed while on battery.";
526 output
= "Data will be indexed while on battery.";
527 index_on_battery
= !index_on_battery
;
531 [ConfigOption (Description
="Add a root path to be indexed", Params
=1, ParamsDescription
="A path")]
532 internal bool AddRoot (out string output
, string [] args
)
534 roots
.Add (args
[0]);
535 output
= "Root added.";
539 [ConfigOption (Description
="Remove an indexing root", Params
=1, ParamsDescription
="A path")]
540 internal bool DelRoot (out string output
, string [] args
)
542 roots
.Remove (args
[0]);
543 output
= "Root removed.";
547 [ConfigOption (Description
="List user-specified resources to be excluded from indexing", IsMutator
=false)]
548 internal bool ListExcludes (out string output
, string [] args
)
550 output
= "User-specified resources to be excluded from indexing:\n";
551 foreach (ExcludeItem exclude_item
in excludes
)
552 output
+= String
.Format (" - [{0}] {1}\n", exclude_item
.Type
.ToString (), exclude_item
.Value
);
556 [ConfigOption (Description
="Add a resource to exclude from indexing", Params
=2, ParamsDescription
="A type [path/pattern/mailfolder], a path/pattern/name")]
557 internal bool AddExclude (out string output
, string [] args
)
561 type
= (ExcludeType
) Enum
.Parse (typeof (ExcludeType
), args
[0], true);
562 } catch (Exception e
) {
563 output
= String
.Format("Invalid type '{0}'. Valid types: Path, Pattern, MailFolder", args
[0]);
567 excludes
.Add (new ExcludeItem (type
, args
[1]));
568 output
= "Exclude added.";
572 [ConfigOption (Description
="Remove an excluded resource", Params
=2, ParamsDescription
="A type [path/pattern/mailfolder], a path/pattern/name")]
573 internal bool DelExclude (out string output
, string [] args
)
577 type
= (ExcludeType
) Enum
.Parse (typeof (ExcludeType
), args
[0], true);
578 } catch (Exception e
) {
579 output
= String
.Format("Invalid type '{0}'. Valid types: Path, Pattern, MailFolder", args
[0]);
583 foreach (ExcludeItem item
in excludes
) {
584 if (item
.Type
!= type
|| item
.Value
!= args
[1])
586 excludes
.Remove (item
);
587 output
= "Exclude removed.";
591 output
= "Could not find requested exclude to remove.";
597 public class Section
{
599 public bool SaveNeeded
= false;
602 private class ConfigOption
: Attribute
{
603 public string Description
;
605 public string ParamsDescription
;
606 public bool IsMutator
= true;
609 private class ConfigSection
: Attribute
{
613 public class ConfigException
: Exception
{
614 public ConfigException (string msg
) : base (msg
) { }
619 //////////////////////////////////////////////////////////////////////
621 public enum ExcludeType
{
627 public class ExcludeItem
{
629 private ExcludeType type
;
633 public ExcludeType Type
{
635 set { type = value; }
638 private string exactMatch
;
639 private string prefix
;
640 private string suffix
;
644 public string Value
{
648 case ExcludeType
.Path
:
649 case ExcludeType
.MailFolder
:
653 case ExcludeType
.Pattern
:
654 if (value.StartsWith ("/") && value.EndsWith ("/")) {
655 regex
= new Regex (value.Substring (1, value.Length
- 2));
659 int i
= value.IndexOf ('*');
664 prefix
= value.Substring (0, i
);
665 if (i
< value.Length
-1)
666 suffix
= value.Substring (i
+1);
675 public ExcludeItem () {}
677 public ExcludeItem (ExcludeType type
, string value) {
682 public bool IsMatch (string param
)
685 case ExcludeType
.Path
:
686 case ExcludeType
.MailFolder
:
687 if (prefix
!= null && ! param
.StartsWith (prefix
))
692 case ExcludeType
.Pattern
:
693 if (exactMatch
!= null)
694 return param
== exactMatch
;
695 if (prefix
!= null && ! param
.StartsWith (prefix
))
697 if (suffix
!= null && ! param
.EndsWith (suffix
))
699 if (regex
!= null && ! regex
.IsMatch (param
))
708 public override bool Equals (object obj
)
710 ExcludeItem exclude
= obj
as ExcludeItem
;
711 return (exclude
!= null && exclude
.Type
== type
&& exclude
.Value
== val
);
714 public override int GetHashCode ()
716 return (this.Value
.GetHashCode () ^
(int) this.Type
);
721 //////////////////////////////////////////////////////////////////////
723 public class KeyBinding
{
727 public bool Ctrl
= false;
729 public bool Alt
= false;
731 public KeyBinding () {}
732 public KeyBinding (string key
) : this (key
, false, false) {}
734 public KeyBinding (string key
, bool ctrl
, bool alt
)
741 public override string ToString ()
755 public string ToReadableString ()
757 return ToString ().Replace (">", "-").Replace ("<", "");