/// /// Copyright © 2003-2008 JetBrains s.r.o. /// You may distribute under the terms of the GNU General Public License, as published by the Free Software Foundation, version 2 (see License.txt in the repository root folder). /// using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Drawing; using System.Text; using System.Windows.Forms; using JetBrains.DataStructures; using JetBrains.Omea.AsyncProcessing; using JetBrains.Omea.Base; using JetBrains.Omea.Containers; using JetBrains.Omea.FiltersManagement; using JetBrains.Omea.GUIControls.CustomViews; using JetBrains.Omea.OpenAPI; using JetBrains.Omea.ResourceTools; using JetBrains.Omea.TextIndex; namespace JetBrains.Omea { #region Generic Text Providers: Annotation and Resource Header /// /// Class which provides annotation text for indexing. /// internal class AnnotationTextIndexProvider : IResourceTextProvider { public bool ProcessResourceText( IResource res, IResourceTextConsumer consumer ) { if( res.HasProp( "Annotation" )) { string anno = res.GetStringProp( "Annotation" ); consumer.RestartOffsetCounting(); consumer.AddDocumentFragment( res.Id, anno, DocumentSection.AnnotationSection ); } return true; } public void RejectResult() {} } /// /// Class which provides annotation text for indexing. /// internal class TitleTextIndexProvider : IResourceTextProvider { public bool ProcessResourceText( IResource res, IResourceTextConsumer consumer ) { string title = res.GetPropText( Core.Props.Name ); consumer.AddDocumentHeading( res.Id, title ); IResource fromPerson = res.GetLinkProp( Core.ContactManager.Props.LinkFrom ); if( fromPerson != null ) { consumer.AddDocumentFragment( res.Id, fromPerson.DisplayName, DocumentSection.SourceSection ); } return true; } public void RejectResult() { } } #endregion Annotation Provider #region MockEnvironment /** * A TextIndexManager stub which is used when OmniaMea is running with no text index. */ internal class MockTextIndexManager: ITextIndexManager { public event EventHandler IndexLoaded; public void QueueImmediateIndexing( int docID, string from, string subject, string body ) {} public void QueryIndexing(int resID) {} public void QueueContextExtraction(IPropertyProvider prov, object e ) {} public void RebuildIndex() {} public void DeleteDocumentQueued(int resID) {} public void DeleteDocumentImmediate(int resID) {} public bool IsIndexPresent() { return false; } public bool IsDocumentInIndex(int docID) { return false; } public void SetUpdateResultHandler( UpdateFinishedEventHandler h ) {} public void SetTextIndexLoadedHandler( EventHandler h ) {} public void ClearTextIndexLoadedHandler( EventHandler h ) {} public bool IdleIndexingMode { get{ return false; } set { } } public void RegisterSearchProvider( ISearchProvider host, string title ) {} public void RegisterSearchProvider( ISearchProvider host, string title, string group ) {} public void UnregisterSearchProvider( ISearchProvider host ) {} public string GetSearchProviderTitle( ISearchProvider host ) { return null; } public ISearchProvider CurrentSearchProvider { get{ return null;} set{} } public ISearchProvider[] GetSearchProviders() { return null; } public string[] GetSearchProviderGroups() { return null; } public ISearchProvider[] GetSearchProvidersInGroup( string group ) { return null; } #region ProcessQuery public IResourceList ProcessQuery( string SearchQuery ) { return null; } public IResourceList ProcessQuery( string SearchQuery, int[] RestrictByIDs, out IHighlightDataProvider hldp, out string[] lastStopList, out string errorMsg ) { hldp = null; lastStopList = null; errorMsg = null; return null; } public IResourceList ProcessQuery( string SearchQuery, int[] RestrictByIDs, bool CalcContexts, out IHighlightDataProvider hldp ) { hldp = null; return null; } public bool MatchQuery( string query, IResource res ) { return false; } #endregion ProcessQuery } #endregion MockEnvironment /** * Class which manages asynchronous building of the full-text index. */ public class TextIndexManager: AsyncProcessor, ITextIndexManager { private const int _cDefaultDaysBetweenDefrags = 3; private const ulong _cFreeSpaceMargin = 50 * 1024 * 1024; private const string _cStandardProvidersGroupName = "Standard Search Providers"; private const string _cOthersProvidersGroupName = ""; private readonly IStatusWriter _statusWriter; private readonly FullTextIndexer _textIndexer; private TextQueriesOptimizationManager _queryManager; private DateTime _lastStatusUpdate = DateTime.MinValue; private readonly HashMap _SearchProviders = new HashMap(); private readonly HashMap _ProvidersGroups = new HashMap(); private ISearchProvider _currentSP = null; private static AbstractJob _switchToIdleJob; private bool _idleWaitingStarted = false; public bool _isJobTraceSuppressed = false; private bool _isCriticalIOCaseFlag = false, _isManuallySuspended = false; private object _isIdleIndexing; private SpinWaitLock _pendingDocsLock = new SpinWaitLock(); private readonly IntHashSet _pendingDocs = new IntHashSet( 1000 ); private readonly DelegateJob _processPendingDocsDelegate; private int _documentsIndexed; public event EventHandler IndexLoaded; #region Ctor and Initialization internal TextIndexManager() : base( false ) { _processPendingDocsDelegate = new DelegateJob( "Indexing documents", new MethodInvoker( ProcessPendingDocs ), new object[] {}); if( Core.ResourceStore.PropTypes.Exist( "QueuedForIndexing" ) ) { Core.ResourceStore.PropTypes.Delete( Core.ResourceStore.PropTypes[ "QueuedForIndexing" ].Id ); } _statusWriter = Core.UIManager.GetStatusWriter( typeof(FullTextIndexer), StatusPane.UI ); _isJobTraceSuppressed = Core.SettingStore.ReadBool( "TextIndexing", "SuppressJobTraces", false ); _textIndexer = new FullTextIndexer(); _textIndexer.IndexLoaded += IndexLoadedNotification; Reenterable = false; ThreadName = "TextIndex AsyncProcessor"; ThreadPriority = System.Threading.ThreadPriority.BelowNormal; ThreadStarted += TextIndexProcessor_ThreadStarted; Core.PluginLoader.RegisterResourceTextProvider( null, new AnnotationTextIndexProvider() ); Core.PluginLoader.RegisterResourceTextProvider( null, new TitleTextIndexProvider() ); // Register predefined search providers CurrentSearchProvider = new OmeaGlobalSearchProvider(); RegisterSearchProvider( CurrentSearchProvider, "Omea Search", _cStandardProvidersGroupName ); RegisterSearchProvider( new OmeaQuickSearchProvider(), "Local Search", _cStandardProvidersGroupName ); DefragmentIndexJob._textIndexManager = IndexingJob._textIndexManager = this; SetupDefragmentationQueue(); _switchToIdleJob = new SwitchToIdleModeJob( this ); QueueSwitchToIdleModeJob(); Core.UIManager.RegisterIndicatorLight( "Text Index Manager", this, 30, MainFrame.LoadIconFromAssembly( "textindex_idle.ico" ), MainFrame.LoadIconFromAssembly( "textindex_busy.ico" ), MainFrame.LoadIconFromAssembly( "textindex_stuck.ico" ) ); } /// /// Typical proxy handler, which simply propagates event from internal /// subsystem to external consumers. /// private void IndexLoadedNotification( object sender, EventArgs e ) { if( IndexLoaded != null ) IndexLoaded( this, EventArgs.Empty ); } private void TextIndexProcessor_ThreadStarted( object sender, EventArgs e ) { _queryManager = new TextQueriesOptimizationManager( this, _textIndexer ); _textIndexer.Initialize(); if( !IsIndexPresent() ) { if( !IdleIndexingMode ) { RebuildIndexImpl(); } } else { FixUnindexedResources(); } ThreadStarted -= TextIndexProcessor_ThreadStarted; ThreadPriority = System.Threading.ThreadPriority.Lowest; Trace.WriteLine( "-- TextIndexManager -- Thread started." ); } public void StartIndexingThread() { Trace.WriteLine( "-- TextIndexManager -- Starting thread." ); StartThread(); } #endregion Ctor and Initialization #region SearchProviders Registration API public void RegisterSearchProvider( ISearchProvider host, string title ) { _SearchProviders[ host ] = new Pair( host.Title, host.Icon ); AddProviderToGroup( host, _cOthersProvidersGroupName ); } public void RegisterSearchProvider( ISearchProvider host, string title, string group ) { _SearchProviders[ host ] = new Pair( host.Title, host.Icon ); AddProviderToGroup( host, group ); } public void UnregisterSearchProvider( ISearchProvider host ) { if( _SearchProviders.Contains( host ) ) _SearchProviders.Remove( host ); } public ISearchProvider CurrentSearchProvider { get{ return _currentSP; } set{ _currentSP = value; } } public string GetSearchProviderTitle( ISearchProvider host ) { HashMap.Entry e = _SearchProviders.GetEntry( host ); return (e == null) ? null : (String) ((Pair) e.Value).First; } public ISearchProvider[] GetSearchProviders() { int i = 0; ISearchProvider[] array = new ISearchProvider[ _SearchProviders.Count ]; foreach( HashMap.Entry e in _SearchProviders ) array[ i++ ] = (ISearchProvider) e.Key; return array; } private void AddProviderToGroup( ISearchProvider host, string group ) { ArrayList hosts; HashMap.Entry e = _ProvidersGroups.GetEntry( group ); if( e == null ) { hosts = new ArrayList(); _ProvidersGroups.Add( group, hosts ); } else hosts = (ArrayList) e.Value; hosts.Add( host ); } //--------------------------------------------------------------------- // Return group names in arbitrary order except that the name of the // standard group always comes first. //--------------------------------------------------------------------- public string[] GetSearchProviderGroups() { ArrayList groups = new ArrayList(); groups.Add( _cStandardProvidersGroupName ); foreach( HashMap.Entry e in _ProvidersGroups ) { if( (string) e.Key != _cStandardProvidersGroupName ) groups.Add( e.Key ); } return (string[]) groups.ToArray( typeof( string )); } public ISearchProvider[] GetSearchProvidersInGroup( string group ) { ArrayList list = (ArrayList)_ProvidersGroups[ group ]; return (ISearchProvider[]) list.ToArray( typeof( ISearchProvider) ); } #endregion SearchProviders Registration API #region Getters/Setters public FullTextIndexer FullTextIndexer { get { return _textIndexer; } } public int UprocessedJobsInQueue { get { return _pendingDocs.Count; } } public bool IdleIndexingMode { get { if( _isIdleIndexing == null ) { _isIdleIndexing = ObjectStore.ReadBool( "TextIndex", "IdleIndexingMode", false ); } return (bool) _isIdleIndexing; } set { bool lastValue = (bool) _isIdleIndexing; _isIdleIndexing = value; if( lastValue && !value ) { QueueJob( JobPriority.Immediate, new MethodInvoker( FixUnindexedResources ) ); } if( lastValue != value ) { ObjectStore.WriteBool( "TextIndex", "IdleIndexingMode", value ); } QueueSwitchToIdleModeJob(); } } public void SetExceptionHandler( AsyncExceptionHandler handler ) { ExceptionHandler = handler; } /// /// NextUpdateFinished event is raised when new portion of documents is merged /// into main or incremental index chunk and thus is available for searching. /// public void SetUpdateResultHandler( UpdateFinishedEventHandler h ) { _textIndexer.NextUpdateFinished += h; } #endregion Getters/Setters #region Idle processing private class SwitchToIdleModeJob: AbstractJob { private readonly TextIndexManager _textIndexManager; public SwitchToIdleModeJob( TextIndexManager textIndexManager ) { _textIndexManager = textIndexManager; } protected override void Execute() { if( _textIndexManager.IdleIndexingMode ) { _textIndexManager.FixUnindexedResources(); _textIndexManager.QueueGotEmpty += _textIndexManager.QueueGotEmptyImpl; } } } private void QueueSwitchToIdleModeJob() { QueueIdleJob( JobPriority.Immediate, _switchToIdleJob ); } private void QueueGotEmptyImpl( object sender, EventArgs e ) { if( !Core.IsSystemIdle ) { QueueSwitchToIdleModeJob(); QueueGotEmpty -= QueueGotEmptyImpl; } } #endregion #region Suspend/Resume Indexing public bool IsIndexingSuspended { get { return _isCriticalIOCaseFlag || _isManuallySuspended; } } public bool IsManuallySuspended { get { return _isManuallySuspended; } } internal void SuspendIndexingByError() { _isCriticalIOCaseFlag = true; } internal void SuspendIndexingByUser() { _isManuallySuspended = true; CancelJobs(); _textIndexer.FlushIndices(); } internal void ResumeIndexingByUser() { _isManuallySuspended = false; IterateOverResources( true ); } #endregion Suspend/Resume Indexing #region Reject And Disposal public override void Dispose() { Trace.WriteLine( "--TextIndexManager -- Dispose has started." ); ThreadFinished += _textIndexProcessor_ThreadFinished; Trace.WriteLine( "--TextIndexManager -- Thread finished Handle is attached." ); base.Dispose(); Trace.WriteLine( "--TextIndexManager -- Dispose has finished." ); } private void _textIndexProcessor_ThreadFinished( object sender, EventArgs e ) { Trace.WriteLine( "--TextIndexManager -- [Thread finished] event has been fired." ); // Build the first chunck if there is no index yet, otherwise it // will be rebuilt once more. if( !IsIndexPresent() ) { EndBatchUpdate(); } Trace.WriteLine( "--TextIndexManager -- Flush indices has started." ); _textIndexer.CloseIndices(); Word.DisposeTermTrie(); Trace.WriteLine( "--TextIndexManager -- Flush indices has finished." ); } // NB: Purpose of this method is for tests only!!! internal void FlushAndCloseIndices() { _textIndexer.CloseIndices(); } #endregion Reject And Disposal #region Defragmentation Control internal void StartDefragmentationWaiting() { Trace.WriteLineIf( !FullTextIndexer._suppTrace, "-- TextIndexManager -- Started waiting for IDLE status" ); _idleWaitingStarted = true; } internal void DefragmentationWaitingEnded() { Trace.WriteLineIf( !FullTextIndexer._suppTrace, "-- TextIndexManager -- Finished waiting for IDLE status" ); _idleWaitingStarted = false; } //--------------------------------------------------------------------- // - Check for defragmentation necessity every hour // - "DaysBetweenDefragmentations" days must pass between two // subsequent defragmentations // - When defragmentation condition takes place, queue IDLE uow so that // it does not disturb any other process. //--------------------------------------------------------------------- private void SetupDefragmentationQueue() { Trace.WriteLineIf( !FullTextIndexer._suppTrace, "TextIndexManager -- Setup defragmentation queue." ); QueueJobAt( DateTime.Now.AddHours( 1.0 ), new MethodInvoker( SetupDefragmentationQueue ) ); int daysBetweenDefrags = Core.SettingStore.ReadInt( "Defragmentation", "Interval", _cDefaultDaysBetweenDefrags ); DateTime lastDefrag = Core.SettingStore.ReadDate( "Defragmentation", "LastDefragmentation", DateTime.MinValue ); if( lastDefrag == DateTime.MinValue ) { Core.SettingStore.WriteDate( "Defragmentation", "LastDefragmentation", DateTime.Now ); } else if( !_idleWaitingStarted && ( lastDefrag.AddDays( daysBetweenDefrags ) < DateTime.Now ) && ( Core.State == CoreState.Running )) { StartDefragmentationWaiting(); Trace.WriteLineIf( !FullTextIndexer._suppTrace, "TextIndexManager -- can queue idle job since last date is [" + lastDefrag + "]" ); QueueIdleJob( new DefragmentIndexJob() ); } } #endregion Defragmentation Control #region EndBatchUpdate internal void EndBatchUpdate() { _documentsIndexed = 0; try { _textIndexer.EndBatchUpdate(); } catch( FormatException ex ) { Core.ReportException( ex, ExceptionReportFlags.AttachLog ); RebuildIndex(); return; } catch( System.IO.IOException ) { Core.UIManager.ShowSimpleMessageBox( "Text Index Operation Failed", "System encountered a serious I/O error while constructing text index." + " Indexing operation will be suspended until next start of the Omea."); SuspendIndexingByError(); return; } if( _statusWriter != null ) _statusWriter.ClearStatus(); if ( IndexLoaded != null ) { IndexLoaded( this, EventArgs.Empty ); } AnalyzeFreeSpace(); } #endregion EndBatchUpdate #region Indexing querying public void QueryIndexing( int docID ) { QueryIndexingImpl( docID, true ); } private void QueryIndexingImpl( int docID, bool invokeProcessingPendingDocs ) { // Put new job only if the corresponding mode is appropriate: // - either index in real time, or // - index in idle mode and if( !IdleIndexingMode || Core.IsSystemIdle ) { // Do not act on a resource which was possibly deleted just // before the indexing. if( docID >= 0 ) { #region Pending Data Processing _pendingDocsLock.Enter(); try { _pendingDocs.Add( docID ); } finally { _pendingDocsLock.Exit(); } #endregion Pending Data Processing if( invokeProcessingPendingDocs ) { QueueProcessingPendingDocs(); } } } } private void QueueProcessingPendingDocs() { QueueJobAt( DateTime.Now.AddSeconds( 3 ), new MethodInvoker( DeferredProcessingPendingDocs ) ); } private void DeferredProcessingPendingDocs() { QueueJob( JobPriority.Lowest, _processPendingDocsDelegate ); } public void QueueContextExtraction( IPropertyProvider provider, object e, string[] lexemes ) { QueueJob( JobPriority.AboveNormal, new CalcContextUOW( (SimplePropertyProvider)provider, (Entry) e, lexemes ) ); } #endregion Indexing querying #region Indexing itself private void ProcessPendingDocs() { int processed = 0; while( ( !IdleIndexingMode || Core.IsSystemIdle ) && !Finished && !IsIndexingSuspended ) { int docId = GetNextDocId(); if( docId == -1 ) break; processed++; if( JobStartingDelegate != null ) { JobStartingDelegate( this, EventArgs.Empty ); } IndexDocument( docId ); if( JobFinishedDelegate != null ) { JobFinishedDelegate( this, EventArgs.Empty ); } if( OutstandingJobs > 0 ) { QueueProcessingPendingDocs(); return; } } if( processed > 0 ) { EndBatchUpdate(); } } private int GetNextDocId() { int docId = -1; _pendingDocsLock.Enter(); try { foreach( IntHashSet.Entry e in _pendingDocs ) { docId = e.Key; break; } if( docId >= 0 ) { _pendingDocs.Remove( docId ); } } finally { _pendingDocsLock.Exit(); } return docId; } private void IndexDocument( int docId ) { string jobNameSaved = _processPendingDocsDelegate.Name; try { IResource resource; try { resource = Core.ResourceStore.LoadResource( docId ); StringBuilder builder = StringBuilderPool.Alloc(); try { builder.Append( "Indexing \"" ); builder.Append( resource.DisplayName ); builder.Append( '\"' ); _processPendingDocsDelegate.Rename(builder.ToString()); } finally { StringBuilderPool.Dispose( builder ); } } catch( StorageException ) { return; // it's OK if the document is already deleted } try { // Do not even try to call outside components in this state. // Let this document reside in the "unflushed.dat" unitl next // run of the program. Core.PluginLoader.InvokeResourceTextProviders( resource, FullTextIndexer ); } catch( System.IO.IOException ) { Core.UIManager.ShowSimpleMessageBox( "Text Index Operation Failed", "System encountered a serious I/O error while constructing text index." + " Indexing operation will be suspended until next start of the Omea."); SuspendIndexingByError(); } catch( FormatException ex ) { Core.ReportBackgroundException( ex ); // RebuildIndex RUNS new job which first discards the // TI completely and then flushes the job queue. RebuildIndex(); } catch( Exception ex ) { Core.ReportBackgroundException( ex ); } _documentsIndexed++; int totalDocs = UprocessedJobsInQueue; string message = "Indexing documents (" + _documentsIndexed + "/" + (totalDocs + _documentsIndexed) + ")"; int percent = _documentsIndexed * 100 / (totalDocs + _documentsIndexed); UpdateProgress( percent, message, null ); } finally { _processPendingDocsDelegate.Rename(jobNameSaved); } } #endregion #region Delete Document public void DeleteDocumentQueued( int resID ) { if( IsIndexPresent() ) { QueueJob( JobPriority.BelowNormal, new DeleteDocUOW( resID ) ); } } internal void DeleteDocumentImmediate( int docID ) { if( IsIndexPresent() ) _textIndexer.DeleteDocument( docID ); } #endregion Delete Document public bool IsIndexPresent() { return _textIndexer.IsIndexPresent; } public bool IsDocumentInIndex( int docID ) { return _textIndexer.IsDocumentPresent( docID ); } public void UpdateProgress( int percent, string message, string timeMessage ) { if( _statusWriter != null ) { DateTime dt = DateTime.Now; TimeSpan tsStatus = dt - _lastStatusUpdate; if ( tsStatus.TotalSeconds > 0.5 ) { _statusWriter.ShowStatus( message ); _lastStatusUpdate = dt; } } } #region Rebuild or Upgrade Index public void RebuildIndex() { UpdateProgress( 0, "Rebuilding Text Index...", null ); RunJob( "Rebuilding Text Index...", new MethodInvoker( RebuildIndexImpl ) ); } private void RebuildIndexImpl() { _statusWriter.ShowStatus( "Rebuilding Text Index..." ); CancelJobs(); _textIndexer.DiscardTextIndex(); IterateOverResources( false ); } private void FixUnindexedResources() { IterateOverResources( true ); } private void IterateOverResources( bool checkInIndex ) { foreach( IResourceType resType in Core.ResourceStore.ResourceTypes ) { if( IsResTypeIndexingConformant( resType ) ) { int count = 0; IResourceList resList = Core.ResourceStore.GetAllResources( resType.Name ); IResourceIdCollection ids = resList.ResourceIds; for( int i = 0; i < ids.Count; ++i ) { int resID = ids[ i ]; // Fix for OM-12527 - illegal resource Ids can be met // during iteration (negative). if( resID >= 0 ) { if( !checkInIndex || !IsDocumentInIndex( resID ) ) { QueryIndexingImpl( resID, false ); count++; } } } Trace.WriteLine( "-- TextIndexManager -- Finished requeuing " + count + " of " + ids.Count + " <" + resType.Name + "> resources in REBUILD phase." ); if( count > 0 ) { QueueProcessingPendingDocs(); } } } } /// /// For a resource to be text-indexed its resource type must conform /// to the following criteria: /// - have valid name /// - be indexable /// - its owner plugin must be loaded /// - even if its plugin is loaded (or the owner may be omitted), it /// must be either a file (for a FilePlugin to be able to index it) or /// have some ITextIndexProvider, specific for this particular /// resource type. /// public static bool IsResTypeIndexingConformant( IResourceType resType ) { return !String.IsNullOrEmpty( resType.Name ) && !resType.HasFlag( ResourceTypeFlags.NoIndex ) && resType.OwnerPluginLoaded && ( resType.HasFlag( ResourceTypeFlags.FileFormat ) || Core.PluginLoader.HasTypedTextProvider( resType.Name )); } #endregion Rebuild or Upgrade Index #region Free Space Analysis private void AnalyzeFreeSpace() { lock( this ) { ulong freeSpace = IOTools.DiskFreeSpaceForUserDB( OMEnv.WorkDir ); if( freeSpace < _cFreeSpaceMargin ) { if( !_isCriticalIOCaseFlag ) { Core.UIManager.ShowSimpleMessageBox( "Text Indexing Failed", "Critical amount of free space is achieved on HD. Text indexing is suspended." ); SuspendIndexingByError(); } } else // Check whether we can restore the ordinary way of indexing // documents - suppose that the user freed enough space for // growing TextIndex structures. if( _isCriticalIOCaseFlag ) { _isCriticalIOCaseFlag = false; } } } #endregion Free Space Analysis #region Query Processing Marshaling public bool MatchQuery( string query, IResource res ) { bool matched = _queryManager.MatchResource( res, query ); return matched; } // Last parameter is used for Jobs disambiguation - when several request // to _textIndexer are put in the queue, there is no other way do distinguish // between these calls than the difference in this parameter. internal delegate FullTextIndexer.QueryResult QueryRequest( string str, int dummy ); public IResourceList ProcessQuery( string query ) { string[] stoplist; string parseError; IHighlightDataProvider hldp; return ProcessQuery( query, null, out hldp, out stoplist, out parseError ); } public IResourceList ProcessQuery( string query, out IHighlightDataProvider hldp ) { string[] stoplist; string parseError; return ProcessQuery( query, null, out hldp, out stoplist, out parseError ); } public IResourceList ProcessQuery( string query, int[] inDocIds, out IHighlightDataProvider hldp, out string[] stopList, out string parseError ) { FullTextIndexer.QueryResult qr = _queryManager.QueryList( query ); hldp = null; stopList = null; parseError = null; IResourceList result = Core.ResourceStore.EmptyResourceList; if( qr != null ) { SimplePropertyProvider provider; result = ConvertResultList( qr.Result, qr.IsSingularTerm, inDocIds, out provider ); if( QueryProcessor.LastSearchLexemes.Length > 0 ) hldp = new SearchHighlightDataProvider( this, qr.Result, provider, QueryProcessor.LastSearchLexemes ); stopList = QueryProcessor.LastStoplist; parseError = qr.ErrorMessage; } return result; } private static IResourceList ConvertResultList( Entry[] entries, bool isSingleTerm, int[] inDocIDs, out SimplePropertyProvider provider ) { List IDs = new List(); provider = new SimplePropertyProvider(); if ( entries != null ) { for( int i = 0; i < entries.Length; i++ ) { if(( inDocIDs == null ) || ( Array.IndexOf( inDocIDs, entries[ i ].DocIndex ) != -1 )) { IDs.Add( entries[ i ].DocIndex ); provider.SetProp( entries[ i ].DocIndex, FullTextIndexer.SearchRankPropId, entries[ i ].TfIdf ); if( !isSingleTerm ) provider.SetProp( entries[ i ].DocIndex, FullTextIndexer.ProximityPropId, entries[ i ].Proximity ); } } } IResourceList result = Core.ResourceStore.ListFromIds( IDs, true ); result.AttachPropertyProvider( provider ); return result; } #endregion Query Processing Marshaling } #region SearchHighlightDataProvider /** * The class which implements IHighlightDataProvider on an array of search results. */ public class SearchHighlightDataProvider: IHighlightDataProvider { private readonly IEnumerable _entries; private readonly TextIndexManager _textIndexManager; private readonly SimplePropertyProvider _provider; private readonly string[] _lexemes; public SearchHighlightDataProvider( IEnumerable entries, SimplePropertyProvider provider, string[] lexemes ) { _entries = entries; _provider = provider; _lexemes = lexemes; } public SearchHighlightDataProvider( TextIndexManager textIndexManager, IEnumerable entries, SimplePropertyProvider provider, string[] lexemes ) { _textIndexManager = textIndexManager; _entries = entries; _provider = provider; _lexemes = lexemes; } public bool GetHighlightData( IResource res, out WordPtr[] words ) { words = null; if( _entries != null ) { foreach( Entry e in _entries ) { if ( e.DocIndex == res.Id ) { ContextCtor.GetHighlightedTerms( e, _lexemes, out words ); return true; } } } return false; } public void RequestContexts( int[] resourceIDs ) { if( Core.SettingStore.ReadBool( "Resources", "ShowSearchContext", true ) && _entries != null ) { Trace.WriteLine( "--- HighlightProvider -- Starting context extraction" ); foreach( Entry e in _entries ) { if( Array.IndexOf( resourceIDs, e.DocIndex ) != -1 ) { _textIndexManager.QueueContextExtraction( _provider, e, _lexemes ); } } } } public string GetContext( IResource res ) { return (string) _provider.GetPropValue( res, FullTextIndexer.ContextPropId ); } public OffsetData[] GetContextHighlightData( IResource res ) { ArrayList list = (ArrayList) _provider.GetPropValue( res, FullTextIndexer.ContextHighlightPropId ); return (list == null) ? null : (OffsetData[]) list.ToArray( typeof( OffsetData )); } } #endregion SearchHighlightDataProvider #region ISearchProvider descendants public class OmeaGlobalSearchProvider : ISearchProvider { private const string _IconRes = "search.ico"; private const string _ProviderTitle = "Search in all Omea resources"; private Icon _providerIcon = null; public string Title { get { return _ProviderTitle; } } public Icon Icon { get { if( _providerIcon == null ) _providerIcon = MainFrame.LoadIconFromAssembly( _IconRes ); return _providerIcon; } } public override string ToString() { return "Global"; } public void ProcessQuery( string query ) { //----------------------------------------------------------------- // Avoid perform any UI (and other, generally) work if we are // shutting down - some components (like DefaultViewPane) may // already be disposed. //----------------------------------------------------------------- if( Core.State != CoreState.Running ) return; //----------------------------------------------------------------- // Extract Search Extension subphrases, extract them out of the // query and convert them into the list of conditions. //----------------------------------------------------------------- int anchorPos; string[] resTypes = null; bool parseSuccessful = false; ArrayList conditions = new ArrayList(); do { string anchor; FindSearchExtension( query, out anchor, out anchorPos ); if( anchorPos != -1 ) parseSuccessful = ParseSearchExtension( anchor, anchorPos, ref query, out resTypes, conditions ); } while(( anchorPos != -1 ) && parseSuccessful ); //----------------------------------------------------------------- // Create condition from the query //----------------------------------------------------------------- IFilterRegistry fMgr = Core.FilterRegistry; IResource queryCondition = ((FilterRegistry) fMgr).CreateStandardConditionAux( null, query, ConditionOp.QueryMatch ); FilterRegistry.ReferCondition2Template( queryCondition, fMgr.Std.BodyMatchesSearchQueryXName ); conditions.Add( queryCondition ); //----------------------------------------------------------------- bool showContexts = Core.SettingStore.ReadBool( "Resources", "ShowSearchContext", true ); bool showDelItems = Core.SettingStore.ReadBool( "Search", "ShowDeletedItems", true ); IResource[] condsList = (IResource[]) conditions.ToArray( typeof(IResource) ); IResource view = Core.ResourceStore.FindUniqueResource( FilterManagerProps.ViewResName, "DeepName", Core.FilterRegistry.ViewNameForSearchResults ); if( view != null ) fMgr.ReregisterView( view, fMgr.ViewNameForSearchResults, resTypes, condsList, null ); else view = fMgr.RegisterView( fMgr.ViewNameForSearchResults, resTypes, condsList, null ); Core.FilterRegistry.SetVisibleInAllTabs( view ); //----------------------------------------------------------------- // Set additional properties characteristic only for "Search Results" // view. //----------------------------------------------------------------- ResourceProxy proxy = new ResourceProxy( view ); proxy.BeginUpdate(); proxy.SetProp( Core.Props.Name, AdvancedSearchForm.SearchViewPrefix + query ); proxy.SetProp( "_DisplayName", AdvancedSearchForm.SearchViewPrefix + query ); proxy.SetProp( Core.Props.ShowDeletedItems, showDelItems); proxy.SetProp( "ShowContexts", showContexts ); proxy.SetProp( "ForceExec", true ); if( Core.SettingStore.ReadBool( "Search", "AutoSwitchToResults", true ) ) proxy.SetProp( "RunToTabIfSingleTyped", true ); else proxy.DeleteProp( "RunToTabIfSingleTyped" ); proxy.EndUpdate(); //----------------------------------------------------------------- // Add new view to the panel // Some steps to specify the correct user-ordering for the new view. //----------------------------------------------------------------- Core.ResourceTreeManager.LinkToResourceRoot( view, int.MinValue ); new UserResourceOrder( Core.ResourceTreeManager.ResourceTreeRoot ).Insert( 0, new int[] {view.Id}, false, null ); //----------------------------------------------------------------- // If we still in the Running mode we can do some UI work... //----------------------------------------------------------------- if( Core.State == CoreState.Running ) { Core.UIManager.BeginUpdateSidebar(); Core.LeftSidebar.ActivateViewPane( StandardViewPanes.ViewsCategories ); Core.UIManager.EndUpdateSidebar(); Core.LeftSidebar.DefaultViewPane.SelectResource( view ); } } private static void FindSearchExtension( string query, out string prep, out int prepPosition ) { prepPosition = -1; prep = null; string[] preps = Core.SearchQueryExtensions.GetAllAnchors(); for( int i = 0; i < preps.Length; i++ ) { int pos = query.LastIndexOf( " " + preps[ i ] + " " ); if( pos > prepPosition ) { prep = preps[ i ]; prepPosition = pos; } } } private static bool ParseSearchExtension( string prep, int prepPosition, ref string query, out string[] resTypes, IList conditions ) { string savedQuery = query; ArrayList types = new ArrayList(); IResource condition = null; string tokens = query.Substring( prepPosition + prep.Length + 2 ); query = query.Substring( 0, prepPosition ); resTypes = null; string[] anchors = tokens.Split( ' ' ); for( int i = 0; i < anchors.Length; i++ ) { string resType = Core.SearchQueryExtensions.GetResourceTypeRestriction( prep, anchors[ i ] ); condition = Core.SearchQueryExtensions.GetSingleTokenRestriction( prep, anchors[ i ] ); if( resType != null ) { types.Add( resType ); } else if( condition != null ) { conditions.Add( condition ); } else { condition = Core.SearchQueryExtensions.GetMatchingFreestyleRestriction( prep, tokens ); if( condition != null ) conditions.Add( condition ); else { // Restore the query to the original text in the case when // no alternative matched. query = savedQuery; } break; } } if( types.Count > 0 ) resTypes = (string[]) types.ToArray( typeof( string )); return (resTypes != null) || (condition != null); } } public class OmeaQuickSearchProvider : ISearchProvider { private const string _ProviderTitle = "Search in the current view"; private Icon _providerIcon = null; public string Title { get { return _ProviderTitle; } } public Icon Icon { get { if( _providerIcon == null ) _providerIcon = MainFrame.LoadIconFromAssembly( "previewright.ico" ); return _providerIcon; } } public override string ToString() { return "local"; } public void ProcessQuery( string query ) { //----------------------------------------------------------------- // Avoid perform any UI (and other, generally) work if we are // shutting down - some components (like DefaultViewPane) may // already be disposed. //----------------------------------------------------------------- if( Core.State != CoreState.Running ) return; IResourceList _resourceListBeforeQuickFind = Core.ResourceBrowser.VisibleResources; const string _captionBeforeQuickFind = "_caption"; IHighlightDataProvider highlightDataProvider; TextIndexManager man = (TextIndexManager) Core.TextIndexManager; IResourceList searchResults = man.ProcessQuery( query, out highlightDataProvider ); searchResults = searchResults.Intersect( _resourceListBeforeQuickFind, true ); string quickFindCaption = query; if ( !quickFindCaption.StartsWith( "\"" ) || !quickFindCaption.EndsWith( "\"" ) ) { quickFindCaption = "'" + quickFindCaption + "'"; } ResourceListDisplayOptions options = new ResourceListDisplayOptions(); options.Caption = "Search results for " + quickFindCaption + " in " + _captionBeforeQuickFind; options.HighlightDataProvider = highlightDataProvider; options.SuppressContexts = true; options.ShowNewspaper = Core.ResourceBrowser.NewspaperVisible; IResource ownerView = Core.ResourceBrowser.OwnerResource; Core.ResourceBrowser.DisplayResourceList( ownerView, searchResults, options ); Core.ResourceBrowser.FocusResourceList(); } } #endregion ISearchProvider descendants }