Add --enable-deletion option to buildindex. If used, buildindex will remove deleted...
[beagle.git] / beagled / LuceneQueryable.cs
blobf4c9c6c6b01f944fc093990a56a7cc62a5cc0eca
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 ("Caught exception in shutdown hook");
162 Logger.Log.Warn (ex);
166 /////////////////////////////////////////
168 virtual public bool AcceptQuery (Query query)
170 // Accept all queries by default.
171 return true;
174 /////////////////////////////////////////
176 virtual protected bool HitIsValid (Uri uri)
178 return true;
181 virtual protected bool HitFilter (Hit hit)
183 return true;
186 /////////////////////////////////////////
188 // DEPRECATED: This does nothing, since everything is now
189 // time-based.
190 virtual protected double RelevancyMultiplier (Hit hit)
192 return 1.0;
195 static protected double HalfLifeMultiplier (DateTime dt, int half_life_days)
197 double days = Math.Abs ((DateTime.Now - dt).TotalDays);
198 if (days < 0)
199 return 1.0f;
200 return Math.Pow (0.5, days / (double) half_life_days);
203 // FIXME: A decaying half-life is a little sketchy, since data
204 // will eventually decay beyond the epsilon and be dropped
205 // from the results entirely, which is almost never what we
206 // want, particularly in searches with a few number of
207 // results. But with a default half-life of 6 months, it'll
208 // take over 13 years to fully decay outside the epsilon on
209 // this multiplier alone.
210 static protected double HalfLifeMultiplier (DateTime time)
212 // Default relevancy half-life is six months.
213 return HalfLifeMultiplier (time, 182);
216 static protected double HalfLifeMultiplierFromProperty (Hit hit,
217 double default_multiplier,
218 params object [] properties)
220 double best_m = -1.0;
222 foreach (object obj in properties) {
223 string key = obj as string;
224 string val = hit [key];
225 if (val != null) {
226 DateTime dt = StringFu.StringToDateTime (val);
227 double this_m;
228 this_m = HalfLifeMultiplier (dt, 182); /* 182 days == six months */
229 if (this_m > best_m)
230 best_m = this_m;
234 if (best_m < 0)
235 best_m = default_multiplier;
236 return best_m;
239 /////////////////////////////////////////
241 // *** FIXME *** FIXME *** FIXME *** FIXME ***
242 // When we rename a directory, we need to somehow
243 // propagate change information to files under that
244 // directory. Example: say that file foo is in
245 // directory bar, and there is an open query that
246 // matches foo. The tile probably says something
247 // like "foo, in folder bar".
248 // Then assume I rename bar to baz. That notification
249 // will go out, so a query matching bar will get
250 // updated... but the query matching foo will not.
251 // What should really happen is that the tile
252 // should change to say "foo, in folder baz".
253 // But making that work will require some hacking
254 // on the QueryResults.
255 // *** FIXME *** FIXME *** FIXME *** FIXME ***
257 private class ChangeData : IQueryableChangeData {
259 // These get fed back to LuceneQueryingDriver.DoQuery
260 // as a search subset, and hence need to be internal
261 // Uris when we are remapping.
262 public ICollection AddedUris;
264 // These get reported directly to clients in
265 // Subtract events, and thus need to be external Uris
266 // when we are remapping.
267 public ICollection RemovedUris;
270 public void DoQuery (Query query,
271 IQueryResult query_result,
272 IQueryableChangeData i_change_data)
274 ChangeData change_data = (ChangeData) i_change_data;
276 ICollection added_uris = null;
278 // Index listeners never return any initial matches.
279 if (change_data == null && query.IsIndexListener)
280 return;
282 if (change_data != null) {
284 if (change_data.RemovedUris != null)
285 query_result.Subtract (change_data.RemovedUris);
287 // If nothing was added, we can safely return now: this change
288 // cannot have any further effect on an outstanding live query.
289 if (change_data.AddedUris == null
290 || change_data.AddedUris.Count == 0)
291 return;
293 added_uris = change_data.AddedUris;
295 // If this is an index listener, we don't need to do a query:
296 // we just build up synthethic hits and add them unconditionally.
297 if (query.IsIndexListener) {
298 ArrayList synthetic_hits = new ArrayList ();
299 foreach (Uri uri in added_uris) {
300 if (our_uri_filter != null && ! our_uri_filter (uri))
301 continue;
302 Hit hit = new Hit ();
303 hit.Uri = uri;
304 if (our_hit_filter != null && ! our_hit_filter (hit))
305 continue;
306 synthetic_hits.Add (hit);
308 if (synthetic_hits.Count > 0)
309 query_result.Add (synthetic_hits);
310 return;
314 Driver.DoQuery (query,
315 query_result,
316 added_uris,
317 our_uri_filter,
318 our_hit_filter);
321 /////////////////////////////////////////
323 protected string GetSnippetFromTextCache (string [] query_terms, Uri uri)
325 // Look up the hit in our text cache. If it is there,
326 // use the cached version to generate a snippet.
328 TextReader reader;
329 reader = TextCache.UserCache.GetReader (uri);
330 if (reader == null)
331 return null;
333 string snippet = SnippetFu.GetSnippet (query_terms, reader);
334 reader.Close ();
336 return snippet;
339 // When remapping, override this with
340 // return GetSnippetFromTextCache (query_terms, remapping_fn (hit.Uri))
341 virtual public string GetSnippet (string [] query_terms, Hit hit)
343 return GetSnippetFromTextCache (query_terms, hit.Uri);
346 /////////////////////////////////////////
348 private int progress_percent = -1;
349 private QueryableState state = QueryableState.Idle;
350 private DateTime last_state_change = DateTime.MinValue;
352 public QueryableStatus GetQueryableStatus ()
354 QueryableStatus status = new QueryableStatus ();
356 status.State = state;
357 status.ProgressPercent = progress_percent;
359 // If we're in read-only mode, query the driver
360 // and not the indexer for the item count.
361 if (indexer == null)
362 status.ItemCount = driver.GetItemCount ();
363 else
364 status.ItemCount = indexer.GetItemCount ();
366 // Frequent state changes are common, and there isn't
367 // a real state machine with continuity when it comes
368 // to the indexing process. A delayed indexing task,
369 // for example, might not actually run for several
370 // seconds after it is scheduled. In this case, the
371 // backend might be in an "Idle" state, but the
372 // indexing process clearly isn't done. To work
373 // around this, we also track the last time the state
374 // changed. If it's less than some threshold, then
375 // we consider ourselves to still be in the process of
376 // indexing.
377 if (state != QueryableState.NotApplicable
378 && (state != QueryableState.Idle
379 || (DateTime.Now - last_state_change).TotalSeconds <= 30))
380 status.IsIndexing = true;
382 return status;
385 public QueryableState State {
386 get { return this.state; }
387 set {
388 //Logger.Log.Debug ("State {0}: {1} -> {2}", this, this.state, value);
390 this.state = value;
391 this.last_state_change = DateTime.Now;
395 public int ProgressPercent {
396 get { return this.progress_percent; }
397 set { this.progress_percent = value; }
400 /////////////////////////////////////////
402 public FileStream ReadDataStream (string name)
404 string path = Path.Combine (Path.Combine (PathFinder.IndexDir, this.IndexName), name);
406 if (!File.Exists (path))
407 return null;
409 return new FileStream (path, System.IO.FileMode.Open, FileAccess.Read);
412 public string ReadDataLine (string name)
414 FileStream stream = ReadDataStream (name);
416 if (stream == null)
417 return null;
419 StreamReader reader = new StreamReader (stream);
420 string line = reader.ReadLine ();
421 reader.Close ();
423 return line;
426 public FileStream WriteDataStream (string name)
428 string path = Path.Combine (Path.Combine (PathFinder.IndexDir, this.IndexName), name);
430 return new FileStream (path, System.IO.FileMode.Create, FileAccess.Write, FileShare.ReadWrite);
433 public void WriteDataLine (string name, string line)
435 if (line == null) {
436 string path = Path.Combine (Path.Combine (PathFinder.IndexDir, this.IndexName), name);
438 if (File.Exists (path))
439 File.Delete (path);
441 return;
444 FileStream stream = WriteDataStream (name);
445 StreamWriter writer = new StreamWriter (stream);
446 writer.WriteLine (line);
447 writer.Close ();
451 //////////////////////////////////////////////////////////////////////////////////
453 // More hooks. These are mostly here for the file system backend.
455 virtual protected bool PreAddIndexableHook (Indexable indexable)
457 // By default, we like everything.
458 return true;
461 // If we are remapping Uris, indexables should be added to the
462 // index with the internal Uri attached. This the receipt
463 // will come back w/ an internal Uri. In order for change
464 // notification to work correctly, we have to map it to
465 // an external Uri.
466 virtual protected void PostAddHook (Indexable indexable, IndexerAddedReceipt receipt)
468 // Does nothing by default
471 virtual protected void PostRemoveHook (Indexable indexable, IndexerRemovedReceipt receipt)
473 // Does nothing by default
476 //////////////////////////////////////////////////////////////////////////////////
478 // Adding a single indexable
480 private class AddTask : Scheduler.Task {
481 LuceneQueryable queryable;
482 Indexable indexable;
484 public AddTask (LuceneQueryable queryable,
485 Indexable indexable)
487 this.queryable = queryable;
488 this.indexable = indexable;
489 this.Tag = indexable.DisplayUri.ToString ();
490 this.Weight = 1;
493 override protected void DoTaskReal ()
495 QueryableState old_state = queryable.State;
496 queryable.State = QueryableState.Indexing;
498 if (queryable.PreAddIndexableHook (indexable)) {
499 queryable.AddIndexable (indexable);
501 if (Priority == Scheduler.Priority.Immediate)
502 queryable.Flush ();
503 else
504 queryable.ConditionalFlush ();
507 queryable.State = old_state;
510 override protected void DoCleanup ()
512 indexable.Cleanup ();
516 public Scheduler.Task NewAddTask (Indexable indexable)
518 AddTask task;
519 task = new AddTask (this, indexable);
520 task.Source = this;
521 return task;
524 //////////////////////////////////////////////////////////////////////////////////
526 // Adding an indexable generator
528 private class AddGeneratorTask : Scheduler.Task {
529 LuceneQueryable queryable;
530 IIndexableGenerator generator;
532 // Hook to be invoked after the IIndexableGenerator
533 // has finished processing a batch of Indexables,
534 // just prior to flushing the driver.
535 Scheduler.Hook pre_flush_hook;
537 public AddGeneratorTask (LuceneQueryable queryable,
538 IIndexableGenerator generator)
540 this.queryable = queryable;
541 this.generator = generator;
542 this.Tag = generator.StatusName;
545 override protected void DoTaskReal ()
547 // Since this is a generator, we want the task to
548 // get re-scheduled after it is run.
549 Reschedule = true;
551 QueryableState old_state = queryable.State;
552 queryable.State = QueryableState.Indexing;
554 // Number of times a null indexable was returned. We don't want
555 // to spin tightly in a loop here if we're not actually indexing
556 // things.
557 int misfires = 0;
559 do {
560 if (! generator.HasNextIndexable ()) {
561 // ...except if there is no more work to do, of course.
562 Reschedule = false;
563 break;
566 Indexable generated;
567 generated = generator.GetNextIndexable ();
569 // Note that the indexable generator can return null.
570 // This means that the generator didn't have an indexable
571 // to return this time through, but it does not mean that
572 // its processing queue is empty.
573 if (generated == null) {
574 misfires++;
576 if (misfires > 179) // Another totally arbitrary number
577 break;
578 else
579 continue;
582 if (queryable.PreAddIndexableHook (generated))
583 queryable.AddIndexable (generated);
584 else
585 generated.Cleanup ();
587 // We keep adding indexables until a flush goes through.
588 } while (! queryable.ConditionalFlush ());
590 generator.PostFlushHook ();
592 queryable.State = old_state;
595 override protected void DoCleanup ()
600 public Scheduler.Task NewAddTask (IIndexableGenerator generator)
602 AddGeneratorTask task;
603 task = new AddGeneratorTask (this, generator);
604 task.Source = this;
605 return task;
608 //////////////////////////////////////////////////////////////////////////////////
610 // There used to be a separate type of task for doing removes.
611 // This is all that remains of that old code.
612 public Scheduler.Task NewRemoveTask (Uri uri)
614 Indexable indexable;
615 indexable = new Indexable (IndexableType.Remove, uri);
617 return NewAddTask (indexable);
620 //////////////////////////////////////////////////////////////////////////////////
622 public Scheduler.Task NewRemoveByPropertyTask (Property prop)
624 PropertyRemovalGenerator prg = new PropertyRemovalGenerator (driver, prop);
626 return NewAddTask (prg);
629 ///////////////////////////////////////////////////////////////////////////////////
632 // An IIndexableGenerator that returns remove Indexables for
633 // all items which match a certain property
636 private class PropertyRemovalGenerator : IIndexableGenerator {
638 private LuceneQueryingDriver driver;
639 private Property prop_to_match;
640 private Uri[] uris_to_remove;
641 private int idx;
643 public PropertyRemovalGenerator (LuceneQueryingDriver driver, Property prop)
645 this.driver = driver;
646 this.prop_to_match = prop;
649 public Indexable GetNextIndexable ()
651 Indexable indexable;
653 indexable = new Indexable (IndexableType.Remove, uris_to_remove [idx]);
654 idx++;
656 return indexable;
659 public bool HasNextIndexable ()
661 if (uris_to_remove == null)
662 uris_to_remove = this.driver.PropertyQuery (this.prop_to_match);
664 if (idx < uris_to_remove.Length)
665 return true;
666 else
667 return false;
670 public string StatusName {
671 get {
672 return String.Format ("Removing {0}={1}", prop_to_match.Key, prop_to_match.Value);
676 public void PostFlushHook () { }
680 //////////////////////////////////////////////////////////////////////////////////
682 // When all other tasks are complete, we need to do a final flush.
683 // We schedule that as a maintenance task.
685 private class FinalFlushTask : Scheduler.Task {
686 LuceneQueryable queryable;
688 public FinalFlushTask (LuceneQueryable queryable)
690 this.queryable = queryable;
694 override protected void DoTaskReal ()
696 queryable.Flush ();
700 private void ScheduleFinalFlush ()
702 if (our_final_flush_task == null) {
703 our_final_flush_task = new FinalFlushTask (this);
705 our_final_flush_task.Tag = "Final Flush for " + IndexName;
706 our_final_flush_task.Priority = Scheduler.Priority.Maintenance;
707 our_final_flush_task.SubPriority = 100; // do this first when starting maintenance
708 our_final_flush_task.Source = this;
711 ThisScheduler.Add (our_final_flush_task);
715 //////////////////////////////////////////////////////////////////////////////////
717 // Optimize the index
719 private class OptimizeTask : Scheduler.Task {
720 LuceneQueryable queryable;
722 public OptimizeTask (LuceneQueryable queryable)
724 this.queryable = queryable;
727 override protected void DoTaskReal ()
729 queryable.Optimize ();
733 public Scheduler.Task NewOptimizeTask ()
735 Scheduler.Task task;
736 task = new OptimizeTask (this);
737 task.Tag = "Optimize " + IndexName;
738 task.Priority = Scheduler.Priority.Maintenance;
739 task.Source = this;
741 return task;
744 private void OnOptimizeAllEvent ()
746 Scheduler.Task task;
747 task = NewOptimizeTask (); // construct an optimizer task
748 task.Priority = Scheduler.Priority.Delayed; // but boost the priority
749 ThisScheduler.Add (task);
752 private void ScheduleOptimize ()
754 double optimize_delay;
755 optimize_delay = 10.0; // minutes
757 if (our_optimize_task == null)
758 our_optimize_task = NewOptimizeTask ();
760 if (OptimizeRightAway || Environment.GetEnvironmentVariable ("BEAGLE_UNDER_BLUDGEON") != null)
761 optimize_delay = 1/120.0; // half a second
763 // Changing the trigger time of an already-scheduled process
764 // does what you would expect.
765 our_optimize_task.TriggerTime = DateTime.Now.AddMinutes (optimize_delay);
767 // Adding the same task more than once is a harmless no-op.
768 ThisScheduler.Add (our_optimize_task);
771 //////////////////////////////////////////////////////////////////////////////////
773 // Other hooks
775 // If this returns true, a task will automatically be created to
776 // add the child.
777 virtual protected bool PreChildAddHook (Indexable child)
779 return true;
782 virtual protected void PreFlushHook (IndexerRequest flushed_request)
785 virtual protected void PostFlushHook (IndexerRequest flushed_request,
786 IndexerReceipt [] receipts)
789 //////////////////////////////////////////////////////////////////////////////////
791 protected void AddIndexable (Indexable indexable)
793 indexable.Source = QueryDriver.GetQueryable (this).Name;
795 lock (request_lock)
796 pending_request.Add (indexable);
798 // Schedule a final flush every time we add anything.
799 // Better safe than sorry.
800 ScheduleFinalFlush ();
803 protected void Optimize ()
805 lock (request_lock) {
806 pending_request.OptimizeIndex = true;
807 Flush ();
811 // Returns true if we actually did flush, false otherwise.
812 protected bool ConditionalFlush ()
814 QueryableState old_state = State;
815 State = QueryableState.Flushing;
817 try {
818 lock (request_lock) {
819 if (pending_request.Count > 37) { // a total arbitrary magic number
820 Flush ();
821 return true;
824 return false;
825 } finally {
826 State = old_state;
830 protected void Flush ()
832 QueryableState old_state = State;
833 State = QueryableState.Flushing;
835 try {
836 DoFlush ();
837 } finally {
838 State = old_state;
842 private void DoFlush ()
844 IndexerRequest flushed_request;
846 lock (request_lock) {
847 if (pending_request.IsEmpty)
848 return;
850 flushed_request = pending_request;
851 pending_request = new IndexerRequest ();
853 // We hold the request_lock when calling PreFlushHook, so
854 // that no other requests can come in until it exits.
855 PreFlushHook (flushed_request);
858 IndexerReceipt [] receipts;
859 receipts = indexer.Flush (flushed_request);
861 PostFlushHook (flushed_request, receipts);
863 // Silently return if we get a null back. This is probably
864 // a bad thing to do.
865 if (receipts == null)
866 return;
868 // Nothing happened (except maybe an optimize, which does not
869 // generate a receipt). Also do nothing.
870 if (receipts.Length == 0)
871 return;
873 // Update the cached count of items in the driver
874 driver.SetItemCount (indexer.GetItemCount ());
876 // Something happened, so schedule an optimize just in case.
877 ScheduleOptimize ();
879 if (fa_store != null)
880 fa_store.BeginTransaction ();
882 ArrayList added_uris = new ArrayList ();
883 ArrayList removed_uris = new ArrayList ();
885 for (int i = 0; i < receipts.Length; ++i) {
887 if (receipts [i] is IndexerAddedReceipt) {
889 IndexerAddedReceipt r;
890 r = (IndexerAddedReceipt) receipts [i];
892 // Add the Uri to the list for our change data
893 // before doing any post-processing.
894 // This ensures that we have internal uris when
895 // we are remapping.
896 added_uris.Add (r.Uri);
898 // Call the appropriate hook
899 try {
900 // Map from internal->external Uris in the PostAddHook
901 PostAddHook (flushed_request.GetByUri (r.Uri), r);
902 } catch (Exception ex) {
903 Logger.Log.Warn ("Caught exception in PostAddHook '{0}' '{1}' '{2}'",
904 r.Uri, r.FilterName, r.FilterVersion);
905 Logger.Log.Warn (ex);
908 // Every added Uri also needs to be listed as removed,
909 // to avoid duplicate hits in the query. Since the
910 // removed Uris need to be external Uris, we add them
911 // to the list *after* post-processing.
912 removed_uris.Add (r.Uri);
915 } else if (receipts [i] is IndexerRemovedReceipt) {
917 IndexerRemovedReceipt r;
918 r = (IndexerRemovedReceipt) receipts [i];
920 // Drop the removed item from the text cache
921 TextCache.UserCache.Delete (r.Uri);
924 // Call the appropriate hook
925 try {
926 PostRemoveHook (flushed_request.GetByUri (r.Uri), r);
927 } catch (Exception ex) {
928 Logger.Log.Warn ("Caught exception in PostRemoveHook '{0}'",
929 r.Uri);
930 Logger.Log.Warn (ex);
933 // Add the removed Uri to the list for our
934 // change data. This will be an external Uri
935 // when we are remapping.
936 removed_uris.Add (r.Uri);
938 } else if (receipts [i] is IndexerChildIndexablesReceipt) {
940 IndexerChildIndexablesReceipt r;
941 r = (IndexerChildIndexablesReceipt) receipts [i];
943 foreach (Indexable child in r.Children) {
944 bool please_add_a_new_task = false;
946 try {
947 please_add_a_new_task = PreChildAddHook (child);
948 } catch (InvalidOperationException ex) {
949 // Queryable does not support adding children
950 } catch (Exception ex) {
951 Logger.Log.Warn ("Caught exception in PreChildAddHook '{0}'", child.DisplayUri);
952 Logger.Log.Warn (ex);
955 if (please_add_a_new_task) {
956 //Logger.Log.Debug ("Adding child {0}", child.Uri);
957 Scheduler.Task task = NewAddTask (child);
958 task.SubPriority = 1;
959 ThisScheduler.Add (task);
960 } else
961 child.Cleanup ();
966 if (fa_store != null)
967 fa_store.CommitTransaction ();
969 // Propagate the change notification to any open queries.
970 if (added_uris.Count > 0 || removed_uris.Count > 0) {
971 ChangeData change_data;
972 change_data = new ChangeData ();
973 change_data.AddedUris = added_uris;
974 change_data.RemovedUris = removed_uris;
976 QueryDriver.QueryableChanged (this, change_data);
980 //////////////////////////////////////////////////////////////////////////////////
983 // It is often convenient to have easy access to a FileAttributeStore
986 virtual protected IFileAttributesStore BuildFileAttributesStore ()
988 if (ExtendedAttribute.Supported)
989 return new FileAttributesStore_ExtendedAttribute (IndexFingerprint);
990 else
991 return new FileAttributesStore_Sqlite (IndexDirectory, IndexFingerprint);
995 public FileAttributesStore FileAttributesStore {
996 get {
997 if (fa_store == null)
998 fa_store = new FileAttributesStore (BuildFileAttributesStore ());
999 return fa_store;
1003 //////////////////////////////////////////////////////////////////////////////////
1005 virtual protected LuceneQueryingDriver BuildLuceneQueryingDriver (string index_name,
1006 int minor_version,
1007 bool read_only_mode)
1009 return new LuceneQueryingDriver (index_name, minor_version, read_only_mode);