4 // Copyright (C) 2004-2005 Novell, Inc.
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.
28 using System
.Collections
;
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 ()
52 //////////////////////////////////////////////////////////
54 public delegate void OptimizeAllHandler ();
56 static private OptimizeAllHandler OptimizeAllEvent
;
58 static public void OptimizeAll ()
60 if (OptimizeAllEvent
!= null)
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.
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
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 ()
156 pending_request
.Cleanup ();
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.
173 /////////////////////////////////////////
175 virtual protected bool HitIsValid (Uri uri
)
180 virtual protected bool HitFilter (Hit hit
)
185 /////////////////////////////////////////
187 // DEPRECATED: This does nothing, since everything is now
189 virtual protected double RelevancyMultiplier (Hit hit
)
194 static protected double HalfLifeMultiplier (DateTime dt
, int half_life_days
)
196 double days
= Math
.Abs ((DateTime
.Now
- dt
).TotalDays
);
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
];
225 DateTime dt
= StringFu
.StringToDateTime (val
);
227 this_m
= HalfLifeMultiplier (dt
, 182); /* 182 days == six months */
234 best_m
= default_multiplier
;
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
)
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)
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) {
303 accept
= our_uri_filter (uri
);
304 } catch (Exception e
) {
305 Log
.Warn (e
, "Caught an exception in HitIsValid for {0}", uri
);
312 Hit hit
= new Hit ();
315 if (our_hit_filter
!= null) {
319 accept
= our_hit_filter (hit
);
320 } catch (Exception e
) {
321 Log
.Warn (e
, "Caught an exception in HitFilter for {0}", hit
.Uri
);
328 synthetic_hits
.Add (hit
);
330 if (synthetic_hits
.Count
> 0)
331 query_result
.Add (synthetic_hits
);
336 Driver
.DoQuery (query
,
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.
351 reader
= TextCache
.UserCache
.GetReader (uri
);
355 string snippet
= SnippetFu
.GetSnippet (query_terms
, reader
);
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.
384 status
.ItemCount
= driver
.GetItemCount ();
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
399 if (state
!= QueryableState
.NotApplicable
400 && (state
!= QueryableState
.Idle
401 || (DateTime
.Now
- last_state_change
).TotalSeconds
<= 30))
402 status
.IsIndexing
= true;
407 public QueryableState State
{
408 get { return this.state; }
410 //Logger.Log.Debug ("State {0}: {1} -> {2}", this, 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
))
431 return new FileStream (path
, System
.IO
.FileMode
.Open
, FileAccess
.Read
);
434 public string ReadDataLine (string name
)
436 FileStream stream
= ReadDataStream (name
);
441 StreamReader reader
= new StreamReader (stream
);
442 string line
= reader
.ReadLine ();
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
)
458 string path
= Path
.Combine (Path
.Combine (PathFinder
.IndexDir
, this.IndexName
), name
);
460 if (File
.Exists (path
))
466 FileStream stream
= WriteDataStream (name
);
467 StreamWriter writer
= new StreamWriter (stream
);
468 writer
.WriteLine (line
);
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.
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
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
;
506 public AddTask (LuceneQueryable queryable
,
509 this.queryable
= queryable
;
510 this.indexable
= indexable
;
511 this.Tag
= indexable
.DisplayUri
.ToString ();
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
)
526 queryable
.ConditionalFlush ();
529 queryable
.State
= old_state
;
532 override protected void DoCleanup ()
534 indexable
.Cleanup ();
538 public Scheduler
.Task
NewAddTask (Indexable indexable
)
541 task
= new AddTask (this, indexable
);
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.
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
577 if (! generator
.HasNextIndexable ()) {
578 // Of course, don't reschedule if there is no more work to do.
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) {
593 if (misfires
> 179) // Another totally arbitrary number
599 if (queryable
.PreAddIndexableHook (generated
))
600 queryable
.AddIndexable (generated
);
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
);
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
)
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
;
660 public PropertyRemovalGenerator (LuceneQueryingDriver driver
, Property prop
)
662 this.driver
= driver
;
663 this.prop_to_match
= prop
;
666 public Indexable
GetNextIndexable ()
670 indexable
= new Indexable (IndexableType
.Remove
, uris_to_remove
[idx
]);
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
)
687 public string StatusName
{
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 ()
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 ()
761 task
= new OptimizeTask (this);
762 task
.Tag
= "Optimize " + IndexName
;
763 task
.Priority
= Scheduler
.Priority
.Maintenance
;
769 private void OnOptimizeAllEvent ()
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;
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 //////////////////////////////////////////////////////////////////////////////////
807 // If this returns true, a task will automatically be created to
809 virtual protected bool PreChildAddHook (Indexable child
)
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
;
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;
843 // Returns true if we actually did flush, false otherwise.
844 protected bool ConditionalFlush ()
846 QueryableState old_state
= State
;
847 State
= QueryableState
.Flushing
;
850 lock (request_lock
) {
851 if (pending_request
.Count
> 37) { // a total arbitrary magic number
862 protected void Flush ()
864 QueryableState old_state
= State
;
865 State
= QueryableState
.Flushing
;
874 private void DoFlush ()
876 IndexerRequest flushed_request
;
878 lock (request_lock
) {
879 if (pending_request
.IsEmpty
)
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)
900 // Nothing happened (except maybe an optimize, which does not
901 // generate a receipt). Also do nothing.
902 if (receipts
.Length
== 0)
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.
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
928 added_uris
.Add (r
.Uri
);
930 // Call the appropriate hook
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
957 PostRemoveHook (flushed_request
.GetByUri (r
.Uri
), r
);
958 } catch (Exception ex
) {
959 Logger
.Log
.Warn (ex
, "Caught exception in PostRemoveHook '{0}'",
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;
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
);
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
);
1020 return new FileAttributesStore_Sqlite (IndexDirectory
, IndexFingerprint
);
1024 public FileAttributesStore FileAttributesStore
{
1026 if (fa_store
== null)
1027 fa_store
= new FileAttributesStore (BuildFileAttributesStore ());
1032 //////////////////////////////////////////////////////////////////////////////////
1034 virtual protected LuceneQueryingDriver
BuildLuceneQueryingDriver (string index_name
,
1036 bool read_only_mode
)
1038 return new LuceneQueryingDriver (index_name
, minor_version
, read_only_mode
);