Dont index style nodes.
[beagle.git] / beagled / LuceneQueryable.cs
blob3bdb86f3d0a7221ce19133a5bcf7eb49093b5ff4
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 public AddGeneratorTask (LuceneQueryable queryable,
533 IIndexableGenerator generator)
535 this.queryable = queryable;
536 this.generator = generator;
537 this.Tag = generator.StatusName;
540 override protected void DoTaskReal ()
542 // Since this is a generator, we want the task to
543 // get re-scheduled after it is run.
544 Reschedule = true;
546 QueryableState old_state = queryable.State;
547 queryable.State = QueryableState.Indexing;
549 // Number of times a null indexable was returned. We don't want
550 // to spin tightly in a loop here if we're not actually indexing
551 // things.
552 int misfires = 0;
554 do {
555 if (! generator.HasNextIndexable ()) {
556 // Of course, don't reschedule if there is no more work to do.
557 Reschedule = false;
558 break;
561 Indexable generated;
562 generated = generator.GetNextIndexable ();
564 // Note that the indexable generator can return null.
565 // This means that the generator didn't have an indexable
566 // to return this time through, but it does not mean that
567 // its processing queue is empty.
568 if (generated == null) {
569 misfires++;
571 if (misfires > 179) // Another totally arbitrary number
572 break;
573 else
574 continue;
577 if (queryable.PreAddIndexableHook (generated))
578 queryable.AddIndexable (generated);
579 else
580 generated.Cleanup ();
582 // We keep adding indexables until a flush goes through.
583 } while (! queryable.ConditionalFlush ());
585 generator.PostFlushHook ();
587 queryable.State = old_state;
590 override protected void DoCleanup ()
595 public Scheduler.Task NewAddTask (IIndexableGenerator generator)
597 AddGeneratorTask task;
598 task = new AddGeneratorTask (this, generator);
599 task.Source = this;
600 return task;
603 //////////////////////////////////////////////////////////////////////////////////
605 // There used to be a separate type of task for doing removes.
606 // This is all that remains of that old code.
607 public Scheduler.Task NewRemoveTask (Uri uri)
609 Indexable indexable;
610 indexable = new Indexable (IndexableType.Remove, uri);
612 return NewAddTask (indexable);
615 //////////////////////////////////////////////////////////////////////////////////
617 public Scheduler.Task NewRemoveByPropertyTask (Property prop)
619 PropertyRemovalGenerator prg = new PropertyRemovalGenerator (driver, prop);
621 return NewAddTask (prg);
624 ///////////////////////////////////////////////////////////////////////////////////
627 // An IIndexableGenerator that returns remove Indexables for
628 // all items which match a certain property
631 private class PropertyRemovalGenerator : IIndexableGenerator {
633 private LuceneQueryingDriver driver;
634 private Property prop_to_match;
635 private Uri[] uris_to_remove;
636 private int idx;
638 public PropertyRemovalGenerator (LuceneQueryingDriver driver, Property prop)
640 this.driver = driver;
641 this.prop_to_match = prop;
644 public Indexable GetNextIndexable ()
646 Indexable indexable;
648 indexable = new Indexable (IndexableType.Remove, uris_to_remove [idx]);
649 idx++;
651 return indexable;
654 public bool HasNextIndexable ()
656 if (uris_to_remove == null)
657 uris_to_remove = this.driver.PropertyQuery (this.prop_to_match);
659 if (idx < uris_to_remove.Length)
660 return true;
661 else
662 return false;
665 public string StatusName {
666 get {
667 return String.Format ("Removing {0}={1}", prop_to_match.Key, prop_to_match.Value);
671 public void PostFlushHook () { }
675 //////////////////////////////////////////////////////////////////////////////////
677 // When all other tasks are complete, we need to do a final flush.
678 // We schedule that as a maintenance task.
680 private class FinalFlushTask : Scheduler.Task {
681 LuceneQueryable queryable;
683 public FinalFlushTask (LuceneQueryable queryable)
685 this.queryable = queryable;
689 override protected void DoTaskReal ()
691 queryable.Flush ();
695 private void ScheduleFinalFlush ()
697 if (our_final_flush_task == null) {
698 our_final_flush_task = new FinalFlushTask (this);
700 our_final_flush_task.Tag = "Final Flush for " + IndexName;
701 our_final_flush_task.Priority = Scheduler.Priority.Maintenance;
702 our_final_flush_task.SubPriority = 100; // do this first when starting maintenance
703 our_final_flush_task.Source = this;
706 ThisScheduler.Add (our_final_flush_task);
710 //////////////////////////////////////////////////////////////////////////////////
712 // Optimize the index
714 private DateTime last_optimize_time = DateTime.MinValue;
716 public DateTime LastOptimizeTime {
717 get { return last_optimize_time; }
718 set { last_optimize_time = value; }
721 private class OptimizeTask : Scheduler.Task {
722 LuceneQueryable queryable;
724 public OptimizeTask (LuceneQueryable queryable)
726 this.queryable = queryable;
729 override protected void DoTaskReal ()
731 queryable.Optimize ();
732 queryable.LastOptimizeTime = DateTime.Now;
736 public Scheduler.Task NewOptimizeTask ()
738 Scheduler.Task task;
739 task = new OptimizeTask (this);
740 task.Tag = "Optimize " + IndexName;
741 task.Priority = Scheduler.Priority.Maintenance;
742 task.Source = this;
744 return task;
747 private void OnOptimizeAllEvent ()
749 Scheduler.Task task;
750 task = NewOptimizeTask (); // construct an optimizer task
751 task.Priority = Scheduler.Priority.Delayed; // but boost the priority
752 ThisScheduler.Add (task);
755 private void ScheduleOptimize ()
757 double optimize_delay;
759 // Really we only want to optimize at most once a day, even if we have
760 // indexed a ton of dat
761 TimeSpan span = DateTime.Now - last_optimize_time;
762 if (span.TotalDays > 1.0)
763 optimize_delay = 10.0; // minutes;
764 else
765 optimize_delay = (new TimeSpan (TimeSpan.TicksPerDay) - span).TotalMinutes;
767 if (our_optimize_task == null)
768 our_optimize_task = NewOptimizeTask ();
770 if (OptimizeRightAway || Environment.GetEnvironmentVariable ("BEAGLE_UNDER_BLUDGEON") != null)
771 optimize_delay = 1/120.0; // half a second
773 // Changing the trigger time of an already-scheduled process
774 // does what you would expect.
775 our_optimize_task.TriggerTime = DateTime.Now.AddMinutes (optimize_delay);
777 // Adding the same task more than once is a harmless no-op.
778 ThisScheduler.Add (our_optimize_task);
781 //////////////////////////////////////////////////////////////////////////////////
783 // Other hooks
785 // If this returns true, a task will automatically be created to
786 // add the child.
787 virtual protected bool PreChildAddHook (Indexable child)
789 return true;
792 virtual protected void PreFlushHook (IndexerRequest flushed_request)
795 virtual protected void PostFlushHook (IndexerRequest flushed_request,
796 IndexerReceipt [] receipts)
799 //////////////////////////////////////////////////////////////////////////////////
801 protected void AddIndexable (Indexable indexable)
803 indexable.Source = QueryDriver.GetQueryable (this).Name;
805 lock (request_lock)
806 pending_request.Add (indexable);
808 // Schedule a final flush every time we add anything.
809 // Better safe than sorry.
810 ScheduleFinalFlush ();
813 protected void Optimize ()
815 lock (request_lock) {
816 pending_request.OptimizeIndex = true;
817 Flush ();
821 // Returns true if we actually did flush, false otherwise.
822 protected bool ConditionalFlush ()
824 QueryableState old_state = State;
825 State = QueryableState.Flushing;
827 try {
828 lock (request_lock) {
829 if (pending_request.Count > 37) { // a total arbitrary magic number
830 Flush ();
831 return true;
834 return false;
835 } finally {
836 State = old_state;
840 protected void Flush ()
842 QueryableState old_state = State;
843 State = QueryableState.Flushing;
845 try {
846 DoFlush ();
847 } finally {
848 State = old_state;
852 private void DoFlush ()
854 IndexerRequest flushed_request;
856 lock (request_lock) {
857 if (pending_request.IsEmpty)
858 return;
860 flushed_request = pending_request;
861 pending_request = new IndexerRequest ();
863 // We hold the request_lock when calling PreFlushHook, so
864 // that no other requests can come in until it exits.
865 PreFlushHook (flushed_request);
868 IndexerReceipt [] receipts;
869 receipts = indexer.Flush (flushed_request);
871 PostFlushHook (flushed_request, receipts);
873 // Silently return if we get a null back. This is probably
874 // a bad thing to do.
875 if (receipts == null)
876 return;
878 // Nothing happened (except maybe an optimize, which does not
879 // generate a receipt). Also do nothing.
880 if (receipts.Length == 0)
881 return;
883 // Update the cached count of items in the driver
884 driver.SetItemCount (indexer.GetItemCount ());
886 // Something happened, so schedule an optimize just in case.
887 ScheduleOptimize ();
889 if (fa_store != null)
890 fa_store.BeginTransaction ();
892 ArrayList added_uris = new ArrayList ();
893 ArrayList removed_uris = new ArrayList ();
895 for (int i = 0; i < receipts.Length; ++i) {
897 if (receipts [i] is IndexerAddedReceipt) {
899 IndexerAddedReceipt r;
900 r = (IndexerAddedReceipt) receipts [i];
902 // Add the Uri to the list for our change data
903 // before doing any post-processing.
904 // This ensures that we have internal uris when
905 // we are remapping.
906 added_uris.Add (r.Uri);
908 // Call the appropriate hook
909 try {
910 // Map from internal->external Uris in the PostAddHook
911 PostAddHook (flushed_request.GetByUri (r.Uri), r);
912 } catch (Exception ex) {
913 Logger.Log.Warn ("Caught exception in PostAddHook '{0}' '{1}' '{2}'",
914 r.Uri, r.FilterName, r.FilterVersion);
915 Logger.Log.Warn (ex);
918 // Every added Uri also needs to be listed as removed,
919 // to avoid duplicate hits in the query. Since the
920 // removed Uris need to be external Uris, we add them
921 // to the list *after* post-processing.
922 removed_uris.Add (r.Uri);
925 } else if (receipts [i] is IndexerRemovedReceipt) {
927 IndexerRemovedReceipt r;
928 r = (IndexerRemovedReceipt) receipts [i];
930 // Drop the removed item from the text cache
931 TextCache.UserCache.Delete (r.Uri);
934 // Call the appropriate hook
935 try {
936 PostRemoveHook (flushed_request.GetByUri (r.Uri), r);
937 } catch (Exception ex) {
938 Logger.Log.Warn ("Caught exception in PostRemoveHook '{0}'",
939 r.Uri);
940 Logger.Log.Warn (ex);
943 // Add the removed Uri to the list for our
944 // change data. This will be an external Uri
945 // when we are remapping.
946 removed_uris.Add (r.Uri);
948 } else if (receipts [i] is IndexerChildIndexablesReceipt) {
950 IndexerChildIndexablesReceipt r;
951 r = (IndexerChildIndexablesReceipt) receipts [i];
953 foreach (Indexable child in r.Children) {
954 bool please_add_a_new_task = false;
956 try {
957 please_add_a_new_task = PreChildAddHook (child);
958 } catch (InvalidOperationException ex) {
959 // Queryable does not support adding children
960 } catch (Exception ex) {
961 Logger.Log.Warn ("Caught exception in PreChildAddHook '{0}'", child.DisplayUri);
962 Logger.Log.Warn (ex);
965 if (please_add_a_new_task) {
966 //Logger.Log.Debug ("Adding child {0}", child.Uri);
967 Scheduler.Task task = NewAddTask (child);
968 task.SubPriority = 1;
969 ThisScheduler.Add (task);
970 } else
971 child.Cleanup ();
976 if (fa_store != null)
977 fa_store.CommitTransaction ();
979 // Propagate the change notification to any open queries.
980 if (added_uris.Count > 0 || removed_uris.Count > 0) {
981 ChangeData change_data;
982 change_data = new ChangeData ();
983 change_data.AddedUris = added_uris;
984 change_data.RemovedUris = removed_uris;
986 QueryDriver.QueryableChanged (this, change_data);
990 //////////////////////////////////////////////////////////////////////////////////
993 // It is often convenient to have easy access to a FileAttributeStore
996 virtual protected IFileAttributesStore BuildFileAttributesStore ()
998 if (ExtendedAttribute.Supported)
999 return new FileAttributesStore_ExtendedAttribute (IndexFingerprint);
1000 else
1001 return new FileAttributesStore_Sqlite (IndexDirectory, IndexFingerprint);
1005 public FileAttributesStore FileAttributesStore {
1006 get {
1007 if (fa_store == null)
1008 fa_store = new FileAttributesStore (BuildFileAttributesStore ());
1009 return fa_store;
1013 //////////////////////////////////////////////////////////////////////////////////
1015 virtual protected LuceneQueryingDriver BuildLuceneQueryingDriver (string index_name,
1016 int minor_version,
1017 bool read_only_mode)
1019 return new LuceneQueryingDriver (index_name, minor_version, read_only_mode);