Fix a fd leak in knotes backend (stupid me) and some cleanup in kaddrbook backend.
[beagle.git] / beagled / LuceneQueryable.cs
blobca2b0b732177b675552b79e5fdf405c3ff2ea491
1 //
2 // LuceneQueryable.cs
3 //
4 // Copyright (C) 2004-2005 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.Collections;
29 using System.IO;
31 using Beagle.Util;
33 namespace Beagle.Daemon {
35 public abstract class LuceneQueryable : IQueryable {
37 static public bool OptimizeRightAway = false;
39 public delegate IIndexer IndexerCreator (string name, int minor_version);
41 static private IndexerCreator indexer_hook = null;
43 static public IndexerCreator IndexerHook {
44 set { indexer_hook = value; }
47 virtual protected IIndexer LocalIndexerHook ()
49 return null;
52 //////////////////////////////////////////////////////////
54 public delegate void OptimizeAllHandler ();
56 static private OptimizeAllHandler OptimizeAllEvent;
58 static public void OptimizeAll ()
60 if (OptimizeAllEvent != null)
61 OptimizeAllEvent ();
64 //////////////////////////////////////////////////////////
66 private Scheduler scheduler = Scheduler.Global;
67 private FileAttributesStore fa_store = null;
69 private string index_name;
70 private int minor_version;
71 private bool read_only_mode;
73 private LuceneQueryingDriver driver;
74 private IIndexer indexer = null;
76 private LuceneQueryingDriver.UriFilter our_uri_filter;
77 private LuceneCommon.HitFilter our_hit_filter;
78 private Scheduler.Task our_final_flush_task = null;
79 private Scheduler.Task our_optimize_task = null;
81 private object request_lock = new object ();
82 private IndexerRequest pending_request = new IndexerRequest ();
84 //////////////////////////////////////////////////////////
86 public LuceneQueryable (string index_name) : this (index_name, -1, false) { }
88 public LuceneQueryable (string index_name, bool read_only_mode) : this (index_name, -1, read_only_mode) { }
90 public LuceneQueryable (string index_name, int minor_version) : this (index_name, minor_version, false) { }
92 public LuceneQueryable (string index_name, int minor_version, bool read_only_mode)
94 this.index_name = index_name;
95 this.minor_version = minor_version;
96 this.read_only_mode = read_only_mode;
98 driver = BuildLuceneQueryingDriver (this.index_name, this.minor_version, this.read_only_mode);
99 our_uri_filter = new LuceneQueryingDriver.UriFilter (this.HitIsValid);
100 our_hit_filter = new LuceneCommon.HitFilter (this.HitFilter);
102 // If the queryable is in read-only more, don't
103 // instantiate an indexer for it.
104 if (read_only_mode)
105 return;
107 indexer = LocalIndexerHook ();
108 if (indexer == null && indexer_hook != null)
109 indexer = indexer_hook (this.index_name, this.minor_version);
111 OptimizeAllEvent += OnOptimizeAllEvent;
113 // Schedule an optimize, just in case
114 ScheduleOptimize ();
116 Shutdown.ShutdownEvent += new Shutdown.ShutdownHandler (OnShutdownEvent);
119 protected string IndexName {
120 get { return index_name; }
123 protected string IndexDirectory {
124 get { return driver.TopDirectory; }
127 protected string IndexFingerprint {
128 get { return driver.Fingerprint; }
131 protected LuceneQueryingDriver Driver {
132 get { return driver; }
135 public Scheduler ThisScheduler {
136 get { return scheduler; }
139 /////////////////////////////////////////
141 virtual public void Start ()
146 /////////////////////////////////////////
148 virtual protected void ShutdownHook ()
153 private void OnShutdownEvent ()
155 lock (request_lock)
156 pending_request.Cleanup ();
158 try {
159 ShutdownHook ();
160 } catch (Exception ex) {
161 Logger.Log.Warn (ex, "Caught exception in shutdown hook");
165 /////////////////////////////////////////
167 virtual public bool AcceptQuery (Query query)
169 // Accept all queries by default.
170 return true;
173 /////////////////////////////////////////
175 virtual protected bool HitIsValid (Uri uri)
177 return true;
180 virtual protected bool HitFilter (Hit hit)
182 return true;
185 /////////////////////////////////////////
187 // DEPRECATED: This does nothing, since everything is now
188 // time-based.
189 virtual protected double RelevancyMultiplier (Hit hit)
191 return 1.0;
194 static protected double HalfLifeMultiplier (DateTime dt, int half_life_days)
196 double days = Math.Abs ((DateTime.Now - dt).TotalDays);
197 if (days < 0)
198 return 1.0f;
199 return Math.Pow (0.5, days / (double) half_life_days);
202 // FIXME: A decaying half-life is a little sketchy, since data
203 // will eventually decay beyond the epsilon and be dropped
204 // from the results entirely, which is almost never what we
205 // want, particularly in searches with a few number of
206 // results. But with a default half-life of 6 months, it'll
207 // take over 13 years to fully decay outside the epsilon on
208 // this multiplier alone.
209 static protected double HalfLifeMultiplier (DateTime time)
211 // Default relevancy half-life is six months.
212 return HalfLifeMultiplier (time, 182);
215 static protected double HalfLifeMultiplierFromProperty (Hit hit,
216 double default_multiplier,
217 params object [] properties)
219 double best_m = -1.0;
221 foreach (object obj in properties) {
222 string key = obj as string;
223 string val = hit [key];
224 if (val != null) {
225 DateTime dt = StringFu.StringToDateTime (val);
226 double this_m;
227 this_m = HalfLifeMultiplier (dt, 182); /* 182 days == six months */
228 if (this_m > best_m)
229 best_m = this_m;
233 if (best_m < 0)
234 best_m = default_multiplier;
235 return best_m;
238 /////////////////////////////////////////
240 // *** FIXME *** FIXME *** FIXME *** FIXME ***
241 // When we rename a directory, we need to somehow
242 // propagate change information to files under that
243 // directory. Example: say that file foo is in
244 // directory bar, and there is an open query that
245 // matches foo. The tile probably says something
246 // like "foo, in folder bar".
247 // Then assume I rename bar to baz. That notification
248 // will go out, so a query matching bar will get
249 // updated... but the query matching foo will not.
250 // What should really happen is that the tile
251 // should change to say "foo, in folder baz".
252 // But making that work will require some hacking
253 // on the QueryResults.
254 // *** FIXME *** FIXME *** FIXME *** FIXME ***
256 private class ChangeData : IQueryableChangeData {
258 // These get fed back to LuceneQueryingDriver.DoQuery
259 // as a search subset, and hence need to be internal
260 // Uris when we are remapping.
261 public ICollection AddedUris;
263 // These get reported directly to clients in
264 // Subtract events, and thus need to be external Uris
265 // when we are remapping.
266 public ICollection RemovedUris;
269 public void DoQuery (Query query,
270 IQueryResult query_result,
271 IQueryableChangeData i_change_data)
273 ChangeData change_data = (ChangeData) i_change_data;
275 ICollection added_uris = null;
277 // Index listeners never return any initial matches.
278 if (change_data == null && query.IsIndexListener)
279 return;
281 if (change_data != null) {
283 if (change_data.RemovedUris != null)
284 query_result.Subtract (change_data.RemovedUris);
286 // If nothing was added, we can safely return now: this change
287 // cannot have any further effect on an outstanding live query.
288 if (change_data.AddedUris == null
289 || change_data.AddedUris.Count == 0)
290 return;
292 added_uris = change_data.AddedUris;
294 // If this is an index listener, we don't need to do a query:
295 // we just build up synthethic hits and add them unconditionally.
296 if (query.IsIndexListener) {
297 ArrayList synthetic_hits = new ArrayList ();
298 foreach (Uri uri in added_uris) {
299 if (our_uri_filter != null) {
300 bool accept = false;
302 try {
303 accept = our_uri_filter (uri);
304 } catch (Exception e) {
305 Log.Warn (e, "Caught an exception in HitIsValid for {0}", uri);
308 if (! accept)
309 continue;
312 Hit hit = new Hit ();
313 hit.Uri = uri;
315 if (our_hit_filter != null) {
316 bool accept = false;
318 try {
319 accept = our_hit_filter (hit);
320 } catch (Exception e) {
321 Log.Warn (e, "Caught an exception in HitFilter for {0}", hit.Uri);
324 if (! accept)
325 continue;
328 synthetic_hits.Add (hit);
330 if (synthetic_hits.Count > 0)
331 query_result.Add (synthetic_hits);
332 return;
336 Driver.DoQuery (query,
337 query_result,
338 added_uris,
339 our_uri_filter,
340 our_hit_filter);
343 /////////////////////////////////////////
345 protected string GetSnippetFromTextCache (string [] query_terms, Uri uri)
347 // Look up the hit in our text cache. If it is there,
348 // use the cached version to generate a snippet.
350 TextReader reader;
351 reader = TextCache.UserCache.GetReader (uri);
352 if (reader == null)
353 return null;
355 string snippet = SnippetFu.GetSnippet (query_terms, reader);
356 reader.Close ();
358 return snippet;
361 // When remapping, override this with
362 // return GetSnippetFromTextCache (query_terms, remapping_fn (hit.Uri))
363 virtual public string GetSnippet (string [] query_terms, Hit hit)
365 return GetSnippetFromTextCache (query_terms, hit.Uri);
368 /////////////////////////////////////////
370 private int progress_percent = -1;
371 private QueryableState state = QueryableState.Idle;
372 private DateTime last_state_change = DateTime.MinValue;
374 public QueryableStatus GetQueryableStatus ()
376 QueryableStatus status = new QueryableStatus ();
378 status.State = state;
379 status.ProgressPercent = progress_percent;
381 // If we're in read-only mode, query the driver
382 // and not the indexer for the item count.
383 if (indexer == null)
384 status.ItemCount = driver.GetItemCount ();
385 else
386 status.ItemCount = indexer.GetItemCount ();
388 // Frequent state changes are common, and there isn't
389 // a real state machine with continuity when it comes
390 // to the indexing process. A delayed indexing task,
391 // for example, might not actually run for several
392 // seconds after it is scheduled. In this case, the
393 // backend might be in an "Idle" state, but the
394 // indexing process clearly isn't done. To work
395 // around this, we also track the last time the state
396 // changed. If it's less than some threshold, then
397 // we consider ourselves to still be in the process of
398 // indexing.
399 if (state != QueryableState.NotApplicable
400 && (state != QueryableState.Idle
401 || (DateTime.Now - last_state_change).TotalSeconds <= 30))
402 status.IsIndexing = true;
404 return status;
407 public QueryableState State {
408 get { return this.state; }
409 set {
410 //Logger.Log.Debug ("State {0}: {1} -> {2}", this, this.state, value);
412 this.state = value;
413 this.last_state_change = DateTime.Now;
417 public int ProgressPercent {
418 get { return this.progress_percent; }
419 set { this.progress_percent = value; }
422 /////////////////////////////////////////
424 public FileStream ReadDataStream (string name)
426 string path = Path.Combine (Path.Combine (PathFinder.IndexDir, this.IndexName), name);
428 if (!File.Exists (path))
429 return null;
431 return new FileStream (path, System.IO.FileMode.Open, FileAccess.Read);
434 public string ReadDataLine (string name)
436 FileStream stream = ReadDataStream (name);
438 if (stream == null)
439 return null;
441 StreamReader reader = new StreamReader (stream);
442 string line = reader.ReadLine ();
443 reader.Close ();
445 return line;
448 public FileStream WriteDataStream (string name)
450 string path = Path.Combine (Path.Combine (PathFinder.IndexDir, this.IndexName), name);
452 return new FileStream (path, System.IO.FileMode.Create, FileAccess.Write, FileShare.ReadWrite);
455 public void WriteDataLine (string name, string line)
457 if (line == null) {
458 string path = Path.Combine (Path.Combine (PathFinder.IndexDir, this.IndexName), name);
460 if (File.Exists (path))
461 File.Delete (path);
463 return;
466 FileStream stream = WriteDataStream (name);
467 StreamWriter writer = new StreamWriter (stream);
468 writer.WriteLine (line);
469 writer.Close ();
473 //////////////////////////////////////////////////////////////////////////////////
475 // More hooks. These are mostly here for the file system backend.
477 virtual protected bool PreAddIndexableHook (Indexable indexable)
479 // By default, we like everything.
480 return true;
483 // If we are remapping Uris, indexables should be added to the
484 // index with the internal Uri attached. This the receipt
485 // will come back w/ an internal Uri. In order for change
486 // notification to work correctly, we have to map it to
487 // an external Uri.
488 virtual protected void PostAddHook (Indexable indexable, IndexerAddedReceipt receipt)
490 // Does nothing by default
493 virtual protected void PostRemoveHook (Indexable indexable, IndexerRemovedReceipt receipt)
495 // Does nothing by default
498 //////////////////////////////////////////////////////////////////////////////////
500 // Adding a single indexable
502 private class AddTask : Scheduler.Task {
503 LuceneQueryable queryable;
504 Indexable indexable;
506 public AddTask (LuceneQueryable queryable,
507 Indexable indexable)
509 this.queryable = queryable;
510 this.indexable = indexable;
511 this.Tag = indexable.DisplayUri.ToString ();
512 this.Weight = 1;
515 override protected void DoTaskReal ()
517 QueryableState old_state = queryable.State;
518 queryable.State = QueryableState.Indexing;
520 if (queryable.PreAddIndexableHook (indexable)) {
521 queryable.AddIndexable (indexable);
523 if (Priority == Scheduler.Priority.Immediate)
524 queryable.Flush ();
525 else
526 queryable.ConditionalFlush ();
529 queryable.State = old_state;
532 override protected void DoCleanup ()
534 indexable.Cleanup ();
538 public Scheduler.Task NewAddTask (Indexable indexable)
540 AddTask task;
541 task = new AddTask (this, indexable);
542 task.Source = this;
543 return task;
546 //////////////////////////////////////////////////////////////////////////////////
548 // Adding an indexable generator
550 private class AddGeneratorTask : Scheduler.Task {
551 LuceneQueryable queryable;
552 IIndexableGenerator generator;
554 public AddGeneratorTask (LuceneQueryable queryable,
555 IIndexableGenerator generator)
557 this.queryable = queryable;
558 this.generator = generator;
559 this.Tag = generator.StatusName;
562 override protected void DoTaskReal ()
564 // Since this is a generator, we want the task to
565 // get re-scheduled after it is run.
566 Reschedule = true;
568 QueryableState old_state = queryable.State;
569 queryable.State = QueryableState.Indexing;
571 // Number of times a null indexable was returned. We don't want
572 // to spin tightly in a loop here if we're not actually indexing
573 // things.
574 int misfires = 0;
576 do {
577 if (! generator.HasNextIndexable ()) {
578 // Of course, don't reschedule if there is no more work to do.
579 Reschedule = false;
580 break;
583 Indexable generated;
584 generated = generator.GetNextIndexable ();
586 // Note that the indexable generator can return null.
587 // This means that the generator didn't have an indexable
588 // to return this time through, but it does not mean that
589 // its processing queue is empty.
590 if (generated == null) {
591 misfires++;
593 if (misfires > 179) // Another totally arbitrary number
594 break;
595 else
596 continue;
599 if (queryable.PreAddIndexableHook (generated))
600 queryable.AddIndexable (generated);
601 else
602 generated.Cleanup ();
604 // We keep adding indexables until a flush goes through.
605 } while (! queryable.ConditionalFlush ());
607 generator.PostFlushHook ();
609 queryable.State = old_state;
612 override protected void DoCleanup ()
617 public Scheduler.Task NewAddTask (IIndexableGenerator generator)
619 AddGeneratorTask task;
620 task = new AddGeneratorTask (this, generator);
621 task.Source = this;
622 return task;
625 //////////////////////////////////////////////////////////////////////////////////
627 // There used to be a separate type of task for doing removes.
628 // This is all that remains of that old code.
629 public Scheduler.Task NewRemoveTask (Uri uri)
631 Indexable indexable;
632 indexable = new Indexable (IndexableType.Remove, uri);
634 return NewAddTask (indexable);
637 //////////////////////////////////////////////////////////////////////////////////
639 public Scheduler.Task NewRemoveByPropertyTask (Property prop)
641 PropertyRemovalGenerator prg = new PropertyRemovalGenerator (driver, prop);
643 return NewAddTask (prg);
646 ///////////////////////////////////////////////////////////////////////////////////
649 // An IIndexableGenerator that returns remove Indexables for
650 // all items which match a certain property
653 private class PropertyRemovalGenerator : IIndexableGenerator {
655 private LuceneQueryingDriver driver;
656 private Property prop_to_match;
657 private Uri[] uris_to_remove;
658 private int idx;
660 public PropertyRemovalGenerator (LuceneQueryingDriver driver, Property prop)
662 this.driver = driver;
663 this.prop_to_match = prop;
666 public Indexable GetNextIndexable ()
668 Indexable indexable;
670 indexable = new Indexable (IndexableType.Remove, uris_to_remove [idx]);
671 idx++;
673 return indexable;
676 public bool HasNextIndexable ()
678 if (uris_to_remove == null)
679 uris_to_remove = this.driver.PropertyQuery (this.prop_to_match);
681 if (idx < uris_to_remove.Length)
682 return true;
683 else
684 return false;
687 public string StatusName {
688 get {
689 return String.Format ("Removing {0}={1}", prop_to_match.Key, prop_to_match.Value);
693 public void PostFlushHook () { }
697 //////////////////////////////////////////////////////////////////////////////////
699 // When all other tasks are complete, we need to do a final flush.
700 // We schedule that as a maintenance task.
702 private class FinalFlushTask : Scheduler.Task {
703 LuceneQueryable queryable;
705 public FinalFlushTask (LuceneQueryable queryable)
707 this.queryable = queryable;
711 override protected void DoTaskReal ()
713 queryable.Flush ();
717 private void ScheduleFinalFlush ()
719 if (our_final_flush_task == null) {
720 our_final_flush_task = new FinalFlushTask (this);
722 our_final_flush_task.Tag = "Final Flush for " + IndexName;
723 our_final_flush_task.Priority = Scheduler.Priority.Maintenance;
724 our_final_flush_task.SubPriority = 100; // do this first when starting maintenance
725 our_final_flush_task.Source = this;
728 ThisScheduler.Add (our_final_flush_task);
732 //////////////////////////////////////////////////////////////////////////////////
734 // Optimize the index
736 private DateTime last_optimize_time = DateTime.MinValue;
738 public DateTime LastOptimizeTime {
739 get { return last_optimize_time; }
740 set { last_optimize_time = value; }
743 private class OptimizeTask : Scheduler.Task {
744 LuceneQueryable queryable;
746 public OptimizeTask (LuceneQueryable queryable)
748 this.queryable = queryable;
751 override protected void DoTaskReal ()
753 queryable.Optimize ();
754 queryable.LastOptimizeTime = DateTime.Now;
758 public Scheduler.Task NewOptimizeTask ()
760 Scheduler.Task task;
761 task = new OptimizeTask (this);
762 task.Tag = "Optimize " + IndexName;
763 task.Priority = Scheduler.Priority.Maintenance;
764 task.Source = this;
766 return task;
769 private void OnOptimizeAllEvent ()
771 Scheduler.Task task;
772 task = NewOptimizeTask (); // construct an optimizer task
773 task.Priority = Scheduler.Priority.Delayed; // but boost the priority
774 ThisScheduler.Add (task);
777 private void ScheduleOptimize ()
779 double optimize_delay;
781 // Really we only want to optimize at most once a day, even if we have
782 // indexed a ton of dat
783 TimeSpan span = DateTime.Now - last_optimize_time;
784 if (span.TotalDays > 1.0)
785 optimize_delay = 10.0; // minutes;
786 else
787 optimize_delay = (new TimeSpan (TimeSpan.TicksPerDay) - span).TotalMinutes;
789 if (our_optimize_task == null)
790 our_optimize_task = NewOptimizeTask ();
792 if (OptimizeRightAway || Environment.GetEnvironmentVariable ("BEAGLE_UNDER_BLUDGEON") != null)
793 optimize_delay = 1/120.0; // half a second
795 // Changing the trigger time of an already-scheduled process
796 // does what you would expect.
797 our_optimize_task.TriggerTime = DateTime.Now.AddMinutes (optimize_delay);
799 // Adding the same task more than once is a harmless no-op.
800 ThisScheduler.Add (our_optimize_task);
803 //////////////////////////////////////////////////////////////////////////////////
805 // Other hooks
807 // If this returns true, a task will automatically be created to
808 // add the child.
809 virtual protected bool PreChildAddHook (Indexable child)
811 return true;
814 virtual protected void PreFlushHook (IndexerRequest flushed_request)
817 virtual protected void PostFlushHook (IndexerRequest flushed_request,
818 IndexerReceipt [] receipts)
821 //////////////////////////////////////////////////////////////////////////////////
823 protected void AddIndexable (Indexable indexable)
825 indexable.Source = QueryDriver.GetQueryable (this).Name;
827 lock (request_lock)
828 pending_request.Add (indexable);
830 // Schedule a final flush every time we add anything.
831 // Better safe than sorry.
832 ScheduleFinalFlush ();
835 protected void Optimize ()
837 lock (request_lock) {
838 pending_request.OptimizeIndex = true;
839 Flush ();
843 // Returns true if we actually did flush, false otherwise.
844 protected bool ConditionalFlush ()
846 QueryableState old_state = State;
847 State = QueryableState.Flushing;
849 try {
850 lock (request_lock) {
851 if (pending_request.Count > 37) { // a total arbitrary magic number
852 Flush ();
853 return true;
856 return false;
857 } finally {
858 State = old_state;
862 protected void Flush ()
864 QueryableState old_state = State;
865 State = QueryableState.Flushing;
867 try {
868 DoFlush ();
869 } finally {
870 State = old_state;
874 private void DoFlush ()
876 IndexerRequest flushed_request;
878 lock (request_lock) {
879 if (pending_request.IsEmpty)
880 return;
882 flushed_request = pending_request;
883 pending_request = new IndexerRequest ();
885 // We hold the request_lock when calling PreFlushHook, so
886 // that no other requests can come in until it exits.
887 PreFlushHook (flushed_request);
890 IndexerReceipt [] receipts;
891 receipts = indexer.Flush (flushed_request);
893 PostFlushHook (flushed_request, receipts);
895 // Silently return if we get a null back. This is probably
896 // a bad thing to do.
897 if (receipts == null)
898 return;
900 // Nothing happened (except maybe an optimize, which does not
901 // generate a receipt). Also do nothing.
902 if (receipts.Length == 0)
903 return;
905 // Update the cached count of items in the driver
906 driver.SetItemCount (indexer.GetItemCount ());
908 // Something happened, so schedule an optimize just in case.
909 ScheduleOptimize ();
911 if (fa_store != null)
912 fa_store.BeginTransaction ();
914 ArrayList added_uris = new ArrayList ();
915 ArrayList removed_uris = new ArrayList ();
917 for (int i = 0; i < receipts.Length; ++i) {
919 if (receipts [i] is IndexerAddedReceipt) {
921 IndexerAddedReceipt r;
922 r = (IndexerAddedReceipt) receipts [i];
924 // Add the Uri to the list for our change data
925 // before doing any post-processing.
926 // This ensures that we have internal uris when
927 // we are remapping.
928 added_uris.Add (r.Uri);
930 // Call the appropriate hook
931 try {
932 // Map from internal->external Uris in the PostAddHook
933 PostAddHook (flushed_request.GetByUri (r.Uri), r);
934 } catch (Exception ex) {
935 Logger.Log.Warn (ex, "Caught exception in PostAddHook '{0}' '{1}' '{2}'",
936 r.Uri, r.FilterName, r.FilterVersion);
939 // Every added Uri also needs to be listed as removed,
940 // to avoid duplicate hits in the query. Since the
941 // removed Uris need to be external Uris, we add them
942 // to the list *after* post-processing.
943 removed_uris.Add (r.Uri);
946 } else if (receipts [i] is IndexerRemovedReceipt) {
948 IndexerRemovedReceipt r;
949 r = (IndexerRemovedReceipt) receipts [i];
951 // Drop the removed item from the text cache
952 TextCache.UserCache.Delete (r.Uri);
955 // Call the appropriate hook
956 try {
957 PostRemoveHook (flushed_request.GetByUri (r.Uri), r);
958 } catch (Exception ex) {
959 Logger.Log.Warn (ex, "Caught exception in PostRemoveHook '{0}'",
960 r.Uri);
963 // Add the removed Uri to the list for our
964 // change data. This will be an external Uri
965 // when we are remapping.
966 removed_uris.Add (r.Uri);
968 } else if (receipts [i] is IndexerChildIndexablesReceipt) {
970 IndexerChildIndexablesReceipt r;
971 r = (IndexerChildIndexablesReceipt) receipts [i];
973 foreach (Indexable child in r.Children) {
974 bool please_add_a_new_task = false;
976 try {
977 please_add_a_new_task = PreChildAddHook (child);
978 } catch (InvalidOperationException ex) {
979 // Queryable does not support adding children
980 } catch (Exception ex) {
981 Logger.Log.Warn (ex, "Caught exception in PreChildAddHook '{0}'", child.DisplayUri);
984 if (please_add_a_new_task) {
985 //Logger.Log.Debug ("Adding child {0}", child.Uri);
986 Scheduler.Task task = NewAddTask (child);
987 task.SubPriority = 1;
988 ThisScheduler.Add (task);
989 } else
990 child.Cleanup ();
995 if (fa_store != null)
996 fa_store.CommitTransaction ();
998 // Propagate the change notification to any open queries.
999 if (added_uris.Count > 0 || removed_uris.Count > 0) {
1000 ChangeData change_data;
1001 change_data = new ChangeData ();
1002 change_data.AddedUris = added_uris;
1003 change_data.RemovedUris = removed_uris;
1005 QueryDriver.QueryableChanged (this, change_data);
1009 //////////////////////////////////////////////////////////////////////////////////
1012 // It is often convenient to have easy access to a FileAttributeStore
1015 virtual protected IFileAttributesStore BuildFileAttributesStore ()
1017 if (ExtendedAttribute.Supported)
1018 return new FileAttributesStore_ExtendedAttribute (IndexFingerprint);
1019 else
1020 return new FileAttributesStore_Sqlite (IndexDirectory, IndexFingerprint);
1024 public FileAttributesStore FileAttributesStore {
1025 get {
1026 if (fa_store == null)
1027 fa_store = new FileAttributesStore (BuildFileAttributesStore ());
1028 return fa_store;
1032 //////////////////////////////////////////////////////////////////////////////////
1034 virtual protected LuceneQueryingDriver BuildLuceneQueryingDriver (string index_name,
1035 int minor_version,
1036 bool read_only_mode)
1038 return new LuceneQueryingDriver (index_name, minor_version, read_only_mode);