///
/// 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.Generic;
using System.Diagnostics;
using System.Windows.Forms;
using JetBrains.DataStructures;
using JetBrains.Omea.OpenAPI;
using JetBrains.Omea.ResourceTools;
namespace JetBrains.Omea.FiltersManagement
{
///
/// Manages updating unread counts for search views.
///
public class ViewUnreadCountProvider: IUnreadCountProvider
{
private readonly UnreadManager _unreadManager;
private readonly IResourceList _allViews;
private readonly IntHashTable _viewToUnreadResources = new IntHashTable(); // view ID -> view unread resources
private readonly Dictionary _unreadResourcesToView = new Dictionary(); // view unread resources -> view resource
public ViewUnreadCountProvider()
{
_unreadManager = Core.UnreadManager as UnreadManager;
_allViews = Core.FilterRegistry.GetViews();
_allViews.ResourceAdded += HandleViewAdd;
_allViews.ResourceDeleting += HandleViewDelete;
_allViews.ResourceChanged += HandleViewChange;
UpdateViews();
SetupMidnightCountersUpdate();
Core.TextIndexManager.IndexLoaded += TextIndexLoaded;
}
private void TextIndexLoaded(object sender, EventArgs e)
{
Core.TextIndexManager.IndexLoaded -= TextIndexLoaded;
UpdateTextViews();
}
public IResourceList GetResourcesForView( IResource viewResource )
{
IResourceList resList = (IResourceList) _viewToUnreadResources [viewResource.Id];
return resList ?? Core.ResourceStore.EmptyResourceList;
}
#region Midnight counter invalidation
private void SetupMidnightCountersUpdate()
{
DateTime startingTime = DateTime.Today.AddDays( 1.0 ).AddSeconds( 5.0 );
Trace.WriteLine( "Queued midnight update of unread counters for " + startingTime );
Core.ResourceAP.QueueJobAt( startingTime, new MethodInvoker( InvalidateCounters ) );
}
private void InvalidateCounters()
{
Trace.WriteLine( "Performing midnight update of unread counters" );
ClearAllList();
UpdateViews();
if( Core.TextIndexManager.IsIndexPresent() )
UpdateTextViews();
foreach( IResource view in Core.ResourceStore.GetAllResources( FilterManagerProps.ViewResName ) )
{
Trace.WriteLine( "Invalidating unread counters for view [" + view.GetPropText( Core.Props.Name ) + "]" );
Core.UnreadManager.InvalidateUnreadCounter( view );
}
SetupMidnightCountersUpdate();
Trace.WriteLine( "Update of unread counters finished" );
}
#endregion Midnight counter invalidation
#region Views list changes
private void HandleViewAdd( object sender, ResourceIndexEventArgs e )
{
UpdateView( e.Resource, false );
}
private void HandleViewDelete( object sender, ResourceIndexEventArgs e )
{
UpdateView( e.Resource, true );
}
private void HandleViewChange( object sender, ResourcePropIndexEventArgs e )
{
UpdateView( e.Resource, false );
}
private void UpdateView( IResource view, bool remove )
{
IResourceList resList = (IResourceList) _viewToUnreadResources[ view.Id ];
if( resList != null ) // possible on view's creation
{
_unreadResourcesToView.Remove( resList );
_viewToUnreadResources.Remove( view.Id );
DetachFromList( resList );
resList.Dispose();
}
if ( !remove && ViewCanBeUnread( view ) )
{
resList = ComputeList( view );
_unreadResourcesToView[ resList ] = view;
_viewToUnreadResources[ view.Id ] = resList;
AttachToList( resList );
_unreadManager.InvalidateUnreadCounter( view );
}
}
#endregion Views list changes
private static bool CanUpdateTextView( IResource view )
{
return ViewCanBeUnread( view ) &&
Core.TextIndexManager.IsIndexPresent() &&
FilterRegistry.HasQueryCondition( view );
}
private void UpdateTextViews()
{
foreach( IResource view in _allViews )
{
if ( CanUpdateTextView( view ) )
{
IResourceList resList = ComputeList( view );
CrossRefItems( view, resList );
AttachToList( resList );
}
}
}
private void UpdateViews()
{
#region Preconditions
if( _unreadResourcesToView.Count != 0 || _viewToUnreadResources.Count != 0 )
throw new ApplicationException( "ViewsUnreadCountProvider -- Contract violation - list are not disposed." );
#endregion Preconditions
// By default, we initially only account for non-trextindex views,
// since text index ones requre handling of the "text index ready" event.
foreach( IResource view in _allViews )
{
if ( ViewCanBeUnread( view ) && !FilterRegistry.HasQueryCondition( view ) )
{
IResourceList resList = ComputeList( view );
CrossRefItems( view, resList );
AttachToList( resList );
}
}
}
private void CrossRefItems( IResource view, IResourceList resList )
{
_unreadResourcesToView[ resList ] = view;
_viewToUnreadResources[ view.Id ] = resList;
}
#region Unread resource in list handling
private void HandleViewUnreadAdded( object sender, ResourceIndexEventArgs e )
{
IResource viewResource = _unreadResourcesToView[ (IResourceList)sender ];
_unreadManager.UpdateCountForView( viewResource, e.Resource, 1 );
}
private void HandleViewUnreadDeleting( object sender, ResourceIndexEventArgs e )
{
IResource viewResource = _unreadResourcesToView[ (IResourceList)sender ];
if ( e.Resource != null )
{
_unreadManager.UpdateCountForView( viewResource, e.Resource, -1 );
}
}
///
/// Handle adding or removal of the resource to/from a workspace. It is indicated by
/// the link with property Core.WorkspaceManager.Props.WorkspaceVisible
///
private void HandleViewUnreadChanged( object sender, ResourcePropIndexEventArgs e )
{
LinkChange[] changes = e.ChangeSet.GetLinkChanges( ((WorkspaceManager) Core.WorkspaceManager).Props.WorkspaceVisible );
if ( changes != null )
{
IResource viewResource = _unreadResourcesToView [ (IResourceList)sender ];
for( int i = 0; i < changes.Length; i++ )
{
_unreadManager.AdjustViewWorkspaceCounter( e.Resource, viewResource, changes[ i ].TargetId,
(changes[ i ].ChangeType == LinkChangeType.Add) ? 1 : -1 );
}
}
}
#endregion Unread resource in list handling
#region Impl
private void ClearAllList()
{
foreach( IResourceList list in _unreadResourcesToView.Keys )
{
DetachFromList( list );
list.Dispose();
}
_unreadResourcesToView.Clear();
_viewToUnreadResources.Clear();
}
private void DetachFromList( IResourceList list )
{
list.ResourceAdded -= HandleViewUnreadAdded;
list.ResourceDeleting -= HandleViewUnreadDeleting;
list.ResourceChanged -= HandleViewUnreadChanged ;
}
private void AttachToList( IResourceList list )
{
list.ResourceAdded += HandleViewUnreadAdded;
list.ResourceDeleting += HandleViewUnreadDeleting;
list.ResourceChanged += HandleViewUnreadChanged;
}
private static IResourceList ComputeList( IResource view )
{
IResourceList list = Core.FilterEngine.ExecView( view, null, SelectionType.Live );
list = ResourceTypeHelper.ExcludeUnloadedPluginResources( list );
list = list.Minus( Core.ResourceStore.FindResourcesWithProp( null, Core.Props.IsDeleted ) );
list = list.Intersect( Core.ResourceStore.FindResourcesWithProp( null, Core.Props.IsUnread ), true );
return list;
}
private static bool ViewCanBeUnread( IResource viewResource )
{
string contentType = viewResource.GetPropText( Core.Props.ContentType );
string contentLinks = viewResource.GetPropText( "ContentLinks" );
if ( contentType.Length > 0 && contentLinks.Length == 0 )
{
string[] contentTypes = contentType.Split( '|' );
for( int i = 0; i < contentTypes.Length; i++ )
{
string ct = contentTypes[ i ];
if ( !Core.ResourceStore.ResourceTypes.Exist( ct ) ||
Core.ResourceStore.ResourceTypes[ ct ].HasFlag( ResourceTypeFlags.CanBeUnread ) )
{
return true;
}
}
return false;
}
return true;
}
#endregion Impl
}
}