Set IO priorities to idle in beagle-build-index and beagle-manage-index
[beagle.git] / beagled / QueryDriver.cs
blobfba4275811157644318430f6423e552b5032b5aa
1 //
2 // QueryDriver.cs
3 //
4 // Copyright (C) 2004 Novell, Inc.
5 //
7 //
8 // Permission is hereby granted, free of charge, to any person obtaining a
9 // copy of this software and associated documentation files (the "Software"),
10 // to deal in the Software without restriction, including without limitation
11 // the rights to use, copy, modify, merge, publish, distribute, sublicense,
12 // and/or sell copies of the Software, and to permit persons to whom the
13 // Software is furnished to do so, subject to the following conditions:
15 // The above copyright notice and this permission notice shall be included in
16 // all 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
23 // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
24 // DEALINGS IN THE SOFTWARE.
27 using System;
28 using System.IO;
29 using System.Collections;
30 using System.Reflection;
31 using System.Text;
32 using System.Threading;
33 using Beagle.Util;
34 namespace Beagle.Daemon {
36 public class QueryDriver {
38 // Enable or Disable specific Queryables by name
40 static ArrayList allowed_queryables = new ArrayList ();
41 static ArrayList denied_queryables = new ArrayList ();
43 static public void Allow (string name)
45 allowed_queryables.Add (name.ToLower ());
48 static public void Deny (string name)
50 denied_queryables.Add (name.ToLower ());
53 static private bool UseQueryable (string name)
55 if (allowed_queryables.Count == 0
56 && denied_queryables.Count == 0)
57 return true;
59 name = name.ToLower ();
61 if (allowed_queryables.Count > 0) {
62 foreach (string allowed in allowed_queryables)
63 if (name == allowed)
64 return true;
65 return false;
68 foreach (string denied in denied_queryables)
69 if (name == denied)
70 return false;
71 return true;
75 //////////////////////////////////////////////////////////////////////////////////////
77 // Paths to static queryables
79 static ArrayList static_queryables = new ArrayList ();
81 static public void AddStaticQueryable (string path) {
83 if (! static_queryables.Contains (path))
84 static_queryables.Add (path);
87 //////////////////////////////////////////////////////////////////////////////////////
89 // Use introspection to find all classes that implement IQueryable, the construct
90 // associated Queryables objects.
92 static ArrayList queryables = new ArrayList ();
93 static Hashtable iqueryable_to_queryable = new Hashtable ();
95 static bool ThisApiSoVeryIsBroken (Type m, object criteria)
97 return m == (Type) criteria;
100 static bool TypeImplementsInterface (Type t, Type iface)
102 Type[] impls = t.FindInterfaces (new TypeFilter (ThisApiSoVeryIsBroken),
103 iface);
104 return impls.Length > 0;
107 // For every type in the assembly that
108 // (1) implements IQueryable
109 // (2) Has a QueryableFlavor attribute attached
110 // assemble a Queryable object and stick it into our list of queryables.
111 static void ScanAssembly (Assembly assembly)
113 int count = 0;
115 foreach (Type type in GetQueryableTypes (assembly)) {
116 foreach (QueryableFlavor flavor in GetQueryableFlavors (type)) {
117 if (! UseQueryable (flavor.Name))
118 continue;
120 if (flavor.RequireInotify && ! Inotify.Enabled) {
121 Logger.Log.Warn ("Can't start backend '{0}' without inotify", flavor.Name);
122 continue;
125 if (flavor.RequireExtendedAttributes && ! ExtendedAttribute.Supported) {
126 Logger.Log.Warn ("Can't start backend '{0}' without extended attributes", flavor.Name);
127 continue;
130 IQueryable iq = null;
131 try {
132 iq = Activator.CreateInstance (type) as IQueryable;
133 } catch (Exception e) {
134 Logger.Log.Error ("Caught exception while instantiating {0} backend", flavor.Name);
135 Logger.Log.Error (e);
138 if (iq != null) {
139 Queryable q = new Queryable (flavor, iq);
140 queryables.Add (q);
141 iqueryable_to_queryable [iq] = q;
142 ++count;
143 break;
147 Logger.Log.Debug ("Found {0} types in {1}", count, assembly.FullName);
150 ////////////////////////////////////////////////////////
152 // Scans PathFinder.SystemIndexesDir after available
153 // system-wide indexes.
154 static void LoadSystemIndexes ()
156 if (!Directory.Exists (PathFinder.SystemIndexesDir))
157 return;
159 int count = 0;
161 foreach (DirectoryInfo index_dir in new DirectoryInfo (PathFinder.SystemIndexesDir).GetDirectories ()) {
162 if (! UseQueryable (index_dir.Name))
163 continue;
165 if (LoadStaticQueryable (index_dir, QueryDomain.System))
166 count++;
169 Logger.Log.Debug ("Found {0} system-wide indexes", count);
172 // Scans configuration for user-specified index paths
173 // to load StaticQueryables from.
174 static void LoadStaticQueryables ()
176 int count = 0;
178 foreach (string path in Conf.Daemon.StaticQueryables)
179 static_queryables.Add (path);
181 foreach (string path in static_queryables) {
182 DirectoryInfo index_dir = new DirectoryInfo (path);
184 if (!index_dir.Exists)
185 continue;
187 // FIXME: QueryDomain might be other than local
188 if (LoadStaticQueryable (index_dir, QueryDomain.Local))
189 count++;
192 Logger.Log.Debug ("Found {0} user-configured static queryables", count);
195 // Instantiates and loads a StaticQueryable from an index directory
196 static private bool LoadStaticQueryable (DirectoryInfo index_dir, QueryDomain query_domain)
198 StaticQueryable static_queryable = null;
200 if (!index_dir.Exists)
201 return false;
203 try {
204 static_queryable = new StaticQueryable (index_dir.Name, index_dir.FullName, true);
205 } catch (Exception e) {
206 Logger.Log.Error ("Caught exception while instantiating static queryable: {0}", index_dir.Name);
207 Logger.Log.Error (e);
208 return false;
211 if (static_queryable != null) {
212 QueryableFlavor flavor = new QueryableFlavor ();
213 flavor.Name = index_dir.Name;
214 flavor.Domain = query_domain;
216 Queryable queryable = new Queryable (flavor, static_queryable);
217 queryables.Add (queryable);
219 iqueryable_to_queryable [static_queryable] = queryable;
221 return true;
224 return false;
227 ////////////////////////////////////////////////////////
229 static private Type[] GetQueryableTypes (Assembly assembly)
231 Type[] assembly_types = assembly.GetTypes ();
232 ArrayList types = new ArrayList (assembly_types.Length);
234 foreach (Type type in assembly_types)
235 if (TypeImplementsInterface (type, typeof (IQueryable)))
236 types.Add (type);
238 return (Type[]) types.ToArray (typeof (Type));
241 static private QueryableFlavor[] GetQueryableFlavors (Type type)
243 object[] attributes = Attribute.GetCustomAttributes (type);
244 ArrayList flavors = new ArrayList (attributes.Length);
246 foreach (object obj in attributes) {
247 QueryableFlavor flavor = obj as QueryableFlavor;
248 if (flavor != null)
249 flavors.Add (flavor);
252 return (QueryableFlavor[]) flavors.ToArray (typeof (QueryableFlavor));
255 static private Assembly[] GetAssemblies ()
257 Assembly[] assemblies;
258 int i = 0;
259 DirectoryInfo backends = new DirectoryInfo (PathFinder.BackendDir);
261 if (backends.Exists) {
262 FileInfo[] assembly_files = backends.GetFiles ("*.dll");
263 assemblies = new Assembly [assembly_files.Length + 1];
265 foreach (FileInfo assembly in assembly_files)
266 assemblies[i++] = Assembly.LoadFile (assembly.ToString ());
268 } else {
269 assemblies = new Assembly [1];
272 assemblies[i] = Assembly.GetExecutingAssembly ();
274 return assemblies;
277 static public void Start ()
279 foreach (Assembly assembly in GetAssemblies ()) {
280 ScanAssembly (assembly);
282 // This allows backends to define their
283 // own executors.
284 Server.ScanAssemblyForExecutors (assembly);
287 // FIXME: This is not nice at all
288 if (UseQueryable ("system"))
289 LoadSystemIndexes ();
290 if (UseQueryable ("static"))
291 LoadStaticQueryables ();
293 foreach (Queryable q in queryables)
294 q.Start ();
297 static public string ListBackends ()
299 string ret = "User:\n";
300 foreach (Assembly assembly in GetAssemblies ())
301 foreach (Type type in GetQueryableTypes (assembly))
302 foreach (QueryableFlavor flavor in GetQueryableFlavors (type))
303 ret += String.Format (" - {0}\n", flavor.Name);
305 if (!Directory.Exists (PathFinder.SystemIndexesDir))
306 return ret;
308 ret += "System:\n";
309 foreach (DirectoryInfo index_dir in new DirectoryInfo (PathFinder.SystemIndexesDir).GetDirectories ()) {
310 ret += String.Format (" - {0}\n", index_dir.Name);
313 return ret;
316 static public Queryable GetQueryable (string name)
318 foreach (Queryable q in queryables) {
319 if (q.Name == name)
320 return q;
323 return null;
326 ////////////////////////////////////////////////////////
328 public delegate void ChangedHandler (Queryable queryable,
329 IQueryableChangeData changeData);
331 static public event ChangedHandler ChangedEvent;
333 // A method to fire the ChangedEvent event.
334 static public void QueryableChanged (IQueryable iqueryable,
335 IQueryableChangeData change_data)
337 if (ChangedEvent != null) {
338 Queryable queryable = iqueryable_to_queryable [iqueryable] as Queryable;
339 ChangedEvent (queryable, change_data);
343 ////////////////////////////////////////////////////////
345 private class MarkAndForwardHits : IQueryResult {
347 IQueryResult result;
348 string name;
350 public MarkAndForwardHits (IQueryResult result, string name)
352 this.result = result;
353 this.name = name;
356 public void Add (ICollection some_hits)
358 foreach (Hit hit in some_hits)
359 if (hit != null)
360 hit.SourceObjectName = name;
361 result.Add (some_hits);
364 public void Subtract (ICollection some_uris)
366 result.Subtract (some_uris);
370 private class QueryClosure : IQueryWorker {
372 Queryable queryable;
373 Query query;
374 IQueryResult result;
375 IQueryableChangeData change_data;
377 public QueryClosure (Queryable queryable,
378 Query query,
379 QueryResult result,
380 IQueryableChangeData change_data)
382 this.queryable = queryable;
383 this.query = query;
384 this.result = new MarkAndForwardHits (result, queryable.Name);
385 this.change_data = change_data;
388 public void DoWork ()
390 queryable.DoQuery (query, result, change_data);
394 static public void DoOneQuery (Queryable queryable,
395 Query query,
396 QueryResult result,
397 IQueryableChangeData change_data)
399 if (queryable.AcceptQuery (query)) {
400 QueryClosure qc = new QueryClosure (queryable, query, result, change_data);
401 result.AttachWorker (qc);
405 static void AddSearchTermInfo (QueryPart part,
406 SearchTermResponse response)
408 if (part.Logic == QueryPartLogic.Prohibited)
409 return;
411 if (part is QueryPart_Or) {
412 ICollection sub_parts;
413 sub_parts = ((QueryPart_Or) part).SubParts;
414 foreach (QueryPart qp in sub_parts)
415 AddSearchTermInfo (qp, response);
416 return;
419 if (! (part is QueryPart_Text))
420 return;
422 QueryPart_Text tp;
423 tp = (QueryPart_Text) part;
425 string [] split;
426 split = tp.Text.Split (' ');
428 // First, remove stop words
429 for (int i = 0; i < split.Length; ++i)
430 if (LuceneCommon.IsStopWord (split [i]))
431 split [i] = null;
433 // Assemble the phrase minus stop words
434 StringBuilder sb;
435 sb = new StringBuilder ();
436 for (int i = 0; i < split.Length; ++i) {
437 if (split [i] == null)
438 continue;
439 if (sb.Length > 0)
440 sb.Append (' ');
441 sb.Append (split [i]);
443 response.ExactText.Add (sb.ToString ());
445 // Now assemble a stemmed version
446 sb.Length = 0; // clear the previous value
447 for (int i = 0; i < split.Length; ++i) {
448 if (split [i] == null)
449 continue;
450 if (sb.Length > 0)
451 sb.Append (' ');
452 sb.Append (LuceneCommon.Stem (split [i]));
454 response.StemmedText.Add (sb.ToString ());
457 ////////////////////////////////////////////////////////
459 static private void DehumanizeQuery (Query query)
461 // We need to remap any QueryPart_Human parts into
462 // lower-level part types. First, we find any
463 // QueryPart_Human parts and explode them into
464 // lower-level types.
465 ArrayList new_parts = null;
466 foreach (QueryPart abstract_part in query.Parts) {
467 if (abstract_part is QueryPart_Human) {
468 QueryPart_Human human = abstract_part as QueryPart_Human;
469 if (new_parts == null)
470 new_parts = new ArrayList ();
471 foreach (QueryPart sub_part in QueryStringParser.Parse (human.QueryString))
472 new_parts.Add (sub_part);
476 // If we found any QueryPart_Human parts, copy the
477 // non-Human parts over and then replace the parts in
478 // the query.
479 if (new_parts != null) {
480 foreach (QueryPart abstract_part in query.Parts) {
481 if (! (abstract_part is QueryPart_Human))
482 new_parts.Add (abstract_part);
485 query.ClearParts ();
486 foreach (QueryPart part in new_parts)
487 query.AddPart (part);
492 static private SearchTermResponse AssembleSearchTermResponse (Query query)
494 SearchTermResponse search_term_response;
495 search_term_response = new SearchTermResponse ();
496 foreach (QueryPart part in query.Parts)
497 AddSearchTermInfo (part, search_term_response);
498 return search_term_response;
501 static private void QueryEachQueryable (Query query,
502 QueryResult result)
504 // The extra pair of calls to WorkerStart/WorkerFinished ensures:
505 // (1) that the QueryResult will fire the StartedEvent
506 // and FinishedEvent, even if no queryable accepts the
507 // query.
508 // (2) that the FinishedEvent will only get called when all of the
509 // backends have had time to finish.
511 object dummy_worker = new object ();
513 if (! result.WorkerStart (dummy_worker))
514 return;
516 foreach (Queryable queryable in queryables)
517 DoOneQuery (queryable, query, result, null);
519 result.WorkerFinished (dummy_worker);
522 static public void DoQueryLocal (Query query,
523 QueryResult result)
525 DehumanizeQuery (query);
527 SearchTermResponse search_term_response;
528 search_term_response = AssembleSearchTermResponse (query);
529 query.ProcessSearchTermResponse (search_term_response);
531 QueryEachQueryable (query, result);
534 static public void DoQuery (Query query,
535 QueryResult result,
536 RequestMessageExecutor.AsyncResponse send_response)
538 DehumanizeQuery (query);
540 SearchTermResponse search_term_response;
541 search_term_response = AssembleSearchTermResponse (query);
542 send_response (search_term_response);
544 QueryEachQueryable (query, result);
547 ////////////////////////////////////////////////////////
549 static public string GetIndexInformation ()
551 StringBuilder builder = new StringBuilder ("\n");
553 foreach (Queryable q in queryables) {
554 builder.AppendFormat ("Name: {0}\n", q.Name);
555 builder.AppendFormat ("Count: {0}\n", q.GetItemCount ());
556 builder.Append ("\n");
559 return builder.ToString ();