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 ("Caught exception in shutdown hook");
162 Logger
.Log
.Warn (ex
);
166 /////////////////////////////////////////
168 virtual public bool AcceptQuery (Query query
)
170 // Accept all queries by default.
174 /////////////////////////////////////////
176 virtual protected bool HitIsValid (Uri uri
)
181 virtual protected bool HitFilter (Hit hit
)
186 /////////////////////////////////////////
188 // DEPRECATED: This does nothing, since everything is now
190 virtual protected double RelevancyMultiplier (Hit hit
)
195 static protected double HalfLifeMultiplier (DateTime dt
, int half_life_days
)
197 double days
= Math
.Abs ((DateTime
.Now
- dt
).TotalDays
);
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
];
226 DateTime dt
= StringFu
.StringToDateTime (val
);
228 this_m
= HalfLifeMultiplier (dt
, 182); /* 182 days == six months */
235 best_m
= default_multiplier
;
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
)
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)
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
))
302 Hit hit
= new Hit ();
304 if (our_hit_filter
!= null && ! our_hit_filter (hit
))
306 synthetic_hits
.Add (hit
);
308 if (synthetic_hits
.Count
> 0)
309 query_result
.Add (synthetic_hits
);
314 Driver
.DoQuery (query
,
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.
329 reader
= TextCache
.UserCache
.GetReader (uri
);
333 string snippet
= SnippetFu
.GetSnippet (query_terms
, reader
);
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.
362 status
.ItemCount
= driver
.GetItemCount ();
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
377 if (state
!= QueryableState
.NotApplicable
378 && (state
!= QueryableState
.Idle
379 || (DateTime
.Now
- last_state_change
).TotalSeconds
<= 30))
380 status
.IsIndexing
= true;
385 public QueryableState State
{
386 get { return this.state; }
388 //Logger.Log.Debug ("State {0}: {1} -> {2}", this, 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
))
409 return new FileStream (path
, System
.IO
.FileMode
.Open
, FileAccess
.Read
);
412 public string ReadDataLine (string name
)
414 FileStream stream
= ReadDataStream (name
);
419 StreamReader reader
= new StreamReader (stream
);
420 string line
= reader
.ReadLine ();
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
)
436 string path
= Path
.Combine (Path
.Combine (PathFinder
.IndexDir
, this.IndexName
), name
);
438 if (File
.Exists (path
))
444 FileStream stream
= WriteDataStream (name
);
445 StreamWriter writer
= new StreamWriter (stream
);
446 writer
.WriteLine (line
);
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.
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
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
;
484 public AddTask (LuceneQueryable queryable
,
487 this.queryable
= queryable
;
488 this.indexable
= indexable
;
489 this.Tag
= indexable
.DisplayUri
.ToString ();
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
)
504 queryable
.ConditionalFlush ();
507 queryable
.State
= old_state
;
510 override protected void DoCleanup ()
512 indexable
.Cleanup ();
516 public Scheduler
.Task
NewAddTask (Indexable indexable
)
519 task
= new AddTask (this, indexable
);
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.
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
560 if (! generator
.HasNextIndexable ()) {
561 // ...except if there is no more work to do, of course.
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) {
576 if (misfires
> 179) // Another totally arbitrary number
582 if (queryable
.PreAddIndexableHook (generated
))
583 queryable
.AddIndexable (generated
);
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
);
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
)
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
;
643 public PropertyRemovalGenerator (LuceneQueryingDriver driver
, Property prop
)
645 this.driver
= driver
;
646 this.prop_to_match
= prop
;
649 public Indexable
GetNextIndexable ()
653 indexable
= new Indexable (IndexableType
.Remove
, uris_to_remove
[idx
]);
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
)
670 public string StatusName
{
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 ()
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 ()
736 task
= new OptimizeTask (this);
737 task
.Tag
= "Optimize " + IndexName
;
738 task
.Priority
= Scheduler
.Priority
.Maintenance
;
744 private void OnOptimizeAllEvent ()
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 //////////////////////////////////////////////////////////////////////////////////
775 // If this returns true, a task will automatically be created to
777 virtual protected bool PreChildAddHook (Indexable child
)
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
;
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;
811 // Returns true if we actually did flush, false otherwise.
812 protected bool ConditionalFlush ()
814 QueryableState old_state
= State
;
815 State
= QueryableState
.Flushing
;
818 lock (request_lock
) {
819 if (pending_request
.Count
> 37) { // a total arbitrary magic number
830 protected void Flush ()
832 QueryableState old_state
= State
;
833 State
= QueryableState
.Flushing
;
842 private void DoFlush ()
844 IndexerRequest flushed_request
;
846 lock (request_lock
) {
847 if (pending_request
.IsEmpty
)
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)
868 // Nothing happened (except maybe an optimize, which does not
869 // generate a receipt). Also do nothing.
870 if (receipts
.Length
== 0)
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.
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
896 added_uris
.Add (r
.Uri
);
898 // Call the appropriate hook
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
926 PostRemoveHook (flushed_request
.GetByUri (r
.Uri
), r
);
927 } catch (Exception ex
) {
928 Logger
.Log
.Warn ("Caught exception in PostRemoveHook '{0}'",
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;
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
);
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
);
991 return new FileAttributesStore_Sqlite (IndexDirectory
, IndexFingerprint
);
995 public FileAttributesStore FileAttributesStore
{
997 if (fa_store
== null)
998 fa_store
= new FileAttributesStore (BuildFileAttributesStore ());
1003 //////////////////////////////////////////////////////////////////////////////////
1005 virtual protected LuceneQueryingDriver
BuildLuceneQueryingDriver (string index_name
,
1007 bool read_only_mode
)
1009 return new LuceneQueryingDriver (index_name
, minor_version
, read_only_mode
);