/// /// 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.Drawing; using System.IO; using System.Reflection; using System.Text; using System.Windows.Forms; using JetBrains.DataStructures; using JetBrains.Omea.Base; using JetBrains.Omea.Contacts; using JetBrains.Omea.GUIControls; using JetBrains.Omea.Charsets; using JetBrains.Omea.Net; using JetBrains.Omea.OpenAPI; using JetBrains.Omea.ResourceStore; using JetBrains.Omea.ResourceTools; using Microsoft.Win32; namespace JetBrains.Omea.Nntp { [PluginDescriptionAttribute("NNTP Newsgroups", "JetBrains Inc.", "Support for Usenet news infrastructure - news servers connection, newsgroups subscribtions, news downloading.", PluginDescriptionFormat.PlainText, "Icons/NntpPluginIcon.png")] public class NntpPlugin : IPlugin, IResourceDisplayer, IStreamProvider, IResourceUIHandler, IResourceDragDropHandler { #region IPlugin Members public void Register() { try { _plugin = this; Settings.LoadSettings(); RegisterTypes(); } catch( Exception e ) { Core.ReportBackgroundException( e ); Core.ActionManager.DisableXmlActionConfiguration( Assembly.GetExecutingAssembly() ); return; } foreach( IResource server in Core.ResourceStore.GetAllResources( _newsServer ).ValidResources ) { NewsFolders.AddToRoot( server ); } IUIManager uiMgr = Core.UIManager; uiMgr.RegisterOptionsGroup( "Internet", "The Internet options enable you to control how [product name] works with several types of online content." ); uiMgr.RegisterOptionsPane( "Internet", "Newsgroups", NntpOptionsPane.NntpOptionsPaneCreator, "The Newsgroups options enable you to control the downloading of newsgroup articles, " + "the default encoding and message format used for newsgroups, and the marking of downloaded messages." ); Core.TabManager.RegisterResourceTypeTab( "News", "News", new[] { _newsArticle, _newsGroup, _newsServer, _newsFolder, _newsLocalArticle }, _propAttachment, 3 ); Image img = Utils.TryGetEmbeddedResourceImageFromAssembly( Assembly.GetExecutingAssembly(), "NntpPlugin.Icons.Newsgroups24.png" ); _newsgroupsTreePane = Core.LeftSidebar.RegisterResourceStructureTreePane( "Newsgroups", "News", "Newsgroups", img, _newsGroup ); _newsgroupsTreePane.WorkspaceFilterTypes = new[] { _newsServer, _newsGroup, _newsFolder }; _newsgroupsTreePane.ToolTipCallback = DisplayGroupOrServerError; Core.LeftSidebar.RegisterViewPaneShortcut( "Newsgroups", Keys.Control | Keys.Alt | Keys.N ); CorrespondentCtrl correspondentPane = new CorrespondentCtrl(); correspondentPane.IniSection = "NNTP"; img = Utils.TryGetEmbeddedResourceImageFromAssembly( Assembly.GetExecutingAssembly(), "NntpPlugin.Icons.Correspondents24.png" ); Core.LeftSidebar.RegisterViewPane( "Correspondents", "News", "Correspondents", img, correspondentPane ); Core.ResourceTreeManager.SetResourceNodeSort( NewsFolders.Root, "NewsSortOrder VisibleOrder DisplayName" ); uiMgr.RegisterResourceSelectPane( _newsGroup, typeof( NewgroupsSelectPane ) ); uiMgr.RegisterResourceLocationLink( _newsArticle, _propTo, _newsGroup ); uiMgr.RegisterResourceLocationLink( _newsGroup, Core.Props.Parent, _newsServer ); uiMgr.RegisterResourceLocationLink( _newsFolder, 0, _newsFolder ); uiMgr.RegisterDisplayInContextHandler( _newsGroup, new DisplayNewsgroupInContextHandler() ); IWorkspaceManager workspaceMgr = Core.WorkspaceManager; workspaceMgr.RegisterWorkspaceType( _newsGroup, new[] { -_propTo }, WorkspaceResourceType.Container ); workspaceMgr.RegisterWorkspaceFolderType( _newsServer, _newsGroup, new[] { -Core.Props.Parent } ); workspaceMgr.RegisterWorkspaceFolderType( _newsFolder, _newsGroup, new[] { -Core.Props.Parent } ); workspaceMgr.RegisterWorkspaceType( _newsArticle, new[] { -_propAttachment }, WorkspaceResourceType.None ); workspaceMgr.RegisterWorkspaceSelectorFilter( _newsGroup, new NewsWorkspaceSelectorFilter() ); IPluginLoader pluginLoader = Core.PluginLoader; IResourceTextProvider articleTextProvider = new ArticleTextProvider(); pluginLoader.RegisterResourceTextProvider( _newsArticle, articleTextProvider ); pluginLoader.RegisterResourceTextProvider( _newsLocalArticle, articleTextProvider ); IResourceRenameHandler renameHandler = new NntpRenameHandler(); pluginLoader.RegisterStreamProvider(_newsArticle, this); pluginLoader.RegisterStreamProvider( _newsLocalArticle, this ); pluginLoader.RegisterResourceDisplayer( _newsArticle, this ); pluginLoader.RegisterResourceDisplayer( _newsLocalArticle, this ); pluginLoader.RegisterResourceUIHandler( _newsGroup, this ); pluginLoader.RegisterResourceUIHandler( _newsFolder, this ); pluginLoader.RegisterResourceUIHandler( _newsServer, this ); pluginLoader.RegisterResourceRenameHandler( _newsGroup, renameHandler ); pluginLoader.RegisterResourceRenameHandler( _newsFolder, renameHandler ); pluginLoader.RegisterResourceRenameHandler( _newsServer, renameHandler ); pluginLoader.RegisterResourceDragDropHandler( _newsGroup, this ); pluginLoader.RegisterResourceDragDropHandler( _newsFolder, this ); pluginLoader.RegisterResourceDragDropHandler( _newsServer, this ); pluginLoader.RegisterResourceDragDropHandler( NewsFolders.Root.Type, this ); pluginLoader.RegisterDefaultThreadingHandler( _newsArticle, Core.Props.Reply ); pluginLoader.RegisterDefaultThreadingHandler( _newsLocalArticle, Core.Props.Reply ); IResourceBrowser resourceBrowser = Core.ResourceBrowser; resourceBrowser.RegisterLinksPaneFilter( _newsArticle, new ItemRecipientsFilter() ); resourceBrowser.RegisterLinksPaneFilter( _newsLocalArticle, new ItemRecipientsFilter() ); resourceBrowser.RegisterLinksPaneFilter( _newsArticle, new NewsAttachmentFilter() ); resourceBrowser.RegisterLinksPaneFilter( _newsLocalArticle, new NewsAttachmentFilter() ); resourceBrowser.RegisterLinksPaneFilter( _newsArticle, new ArticleNewsgroupsFilter() ); resourceBrowser.RegisterLinksPaneFilter( _newsLocalArticle, new ArticleNewsgroupsFilter() ); resourceBrowser.ContentChanged += resourceBrowser_ContentChanged; IResourceList folders = Core.ResourceStore.GetAllResources( _newsFolder ); foreach( IResource folder in folders ) { if ( NewsFolders.IsDefaultFolder( folder ) ) { folder.SetProp( "VisibleInAllWorkspaces", true ); } } IResourceIconManager iconManager = Core.ResourceIconManager; NewsgroupIconProvider iconProvider = new NewsgroupIconProvider(); iconManager.RegisterPropTypeIcon( _propAttachment, LoadNewsIcon( "news_attachment.ico" ) ); iconManager.RegisterResourceIconProvider( _newsGroup, iconProvider ); iconManager.RegisterOverlayIconProvider( _newsGroup, iconProvider ); iconManager.RegisterOverlayIconProvider( _newsArticle, new ArticleOverlayIconProvider() ); pluginLoader.RegisterResourceDeleter( _newsArticle, new NewsArticleDeleter() ); pluginLoader.RegisterResourceDeleter( _newsLocalArticle, new NewsArticleDeleter() ); Core.ProtocolHandlerManager.RegisterProtocolHandler( "news", "newsgroup client", RemoteHandleNews, MakeDefaultHandler ); Core.ProtocolHandlerManager.RegisterProtocolHandler( "snews", "newsgroup client", RemoteHandleSnews ); //----------------------------------------------------------------- // Register classes which [upgrade] and initialize plugin-dependent // conditions, templates, notification and tray icon rules. // Naturally, upgrade initializer must be called first. //----------------------------------------------------------------- Core.PluginLoader.RegisterViewsConstructor( new NewsUpgrade1ViewsConstructor() ); Core.PluginLoader.RegisterViewsConstructor( new NewsViewsConstructor() ); //----------------------------------------------------------------- // Register Search Extensions to narrow the list of results using // simple phrases in search queries: for restricting the resource // type to news articles. //----------------------------------------------------------------- Core.SearchQueryExtensions.RegisterResourceTypeRestriction( "in", "news", _newsArticle ); // Register Link Id which serves as an anchor for tracing the events // when new article is created and linked to its folder. Core.ExpirationRuleManager.RegisterResourceType( _propTo, _newsGroup, _newsArticle ); Core.DisplayColumnManager.SetAlignTopLevelItems( _newsArticle, true ); Core.DisplayColumnManager.SetAlignTopLevelItems( _newsLocalArticle, true ); try { _newsProtocolsKey = Registry.LocalMachine.CreateSubKey( "Software\\Clients\\News\\" + Core.ProductFullName + "\\Protocols" ); } catch { _newsProtocolsKey = null; } Core.ResourceBrowser.SetDefaultViewSettings( "News", AutoPreviewMode.Off, true ); } public void Startup() { RegisterDisplayColumns(); MyPalStorage store = MyPalStorage.Storage; store.CachePredicate( store.FindResourcesWithPropLive( null, _propHasNoBody ) ); string charset = Settings.Charset; if( String.IsNullOrEmpty( charset ) ) Settings.Charset.Save( CharsetsEnum.GetDefaultBodyCharset().Name ); _productType = ( Core.ProductFullName.EndsWith( "Reader" ) ) ? ProductType.Reader : ProductType.Pro; CheckGroups(); //----------------------------------------------------------------- // Decorators should be present here since some of them use live lists // constructed by means e.g. FilterRegistry and local views constructor // helper which might not be initialized on registering phase. //----------------------------------------------------------------- ((JetResourceTreePane)_newsgroupsTreePane).AddNodeDecorator( new NewsNodeDecorator() ); ((JetResourceTreePane)_newsgroupsTreePane).AddNodeDecorator( new TotalCountDecorator( _newsGroup, _propTo, Core.Props.Parent ) ); ((JetResourceTreePane)_newsgroupsTreePane).InsertNodeDecorator( new WatchedArticlesDecorator(), 0 ); Core.StateChanged += Core_StateChanged; } public void Shutdown() { NntpConnectionPool.CloseAll(); Settings.FirstStart.Save( false ); } #endregion #region IResourceDisplayer Members public IDisplayPane CreateDisplayPane( string resourceType ) { if( resourceType == _newsArticle || resourceType == _newsLocalArticle ) { if( _previewPane == null ) { _previewPane = new ArticlePreviewPane(); } return _previewPane; } return null; } #endregion #region IStreamProvider methods public Stream GetResourceStream( IResource res ) { return res.GetBlobProp( _propContent ); } #endregion #region IResourceUIHandler Members public void ResourceNodeSelected( IResource res ) { if( res.Type == _newsGroup ) { NntpClientHelper.DownloadHeadersFromGroup( res ); } if( res.Type == _newsFolder ) { IResourceList groups = new NewsTreeNode( res ).Groups; foreach( IResource group in groups ) { NntpClientHelper.DownloadHeadersFromGroup( group ); } } IResourceList articles = CollectArticles( res, true ); string caption; if( !res.HasProp( Core.Props.DisplayUnread ) ) { caption = "Articles in " + res.DisplayName; } else { caption = "Unread Articles in " + res.DisplayName; articles = articles.Intersect( Core.ResourceStore.FindResourcesWithProp( SelectionType.LiveSnapshot, null, Core.Props.IsUnread ), true ); } IResource lastSelectedArticle = null; int lastSelectedID = res.GetIntProp( _propLastSelectedArticle ); if( lastSelectedID > 0 ) { lastSelectedArticle = Core.ResourceStore.TryLoadResource( lastSelectedID ); } if( !res.HasProp( Core.Props.DisplayThreaded ) || NewsFolders.IsDefaultFolder( res ) ) { Core.ResourceBrowser.DisplayResourceList( res, articles, caption, null, lastSelectedArticle ); } else { Core.ResourceBrowser.DisplayThreadedResourceList( res, articles, caption, "Date From", Core.Props.Reply, null, lastSelectedArticle ); } } public bool CanRenameResource( IResource res ) { return false; } public bool ResourceRenamed( IResource res, string newName ) { return false; } public void ResourcesDropped( IResource targetResource, IResourceList droppedResources ) { return; } public bool CanDropResources( IResource targetResource, IResourceList dragResources ) { return false; } #endregion #region news: protocol handling private delegate void StringResourceDelegate( string str, IResource res ); public void RemoteHandleNews( string url ) { Core.UIManager.QueueUIJob( new HandleNewsURLDelegate( HandleNewsURL ), url, false ); } public void RemoteHandleSnews( string url ) { Core.UIManager.QueueUIJob( new HandleNewsURLDelegate( HandleNewsURL ), url, true ); } private static void MakeDefaultHandler() { try { if( _newsProtocolsKey != null ) { RegistryKey key; using( key = Registry.LocalMachine.OpenSubKey( "Software\\Clients\\News", true ) ) { key.SetValue( null, Core.ProductFullName ); } using( key = Registry.LocalMachine.OpenSubKey( "Software\\Clients\\News\\" + Core.ProductFullName, true ) ) { key.SetValue( null, Core.ProductFullName ); } ArrayList keys = new ArrayList( 3 ); key =_newsProtocolsKey.CreateSubKey( "news" ); key.SetValue( null, "URL:News Protocol" ); keys.Add( key ); key = _newsProtocolsKey.CreateSubKey( "nntp" ); key.SetValue( null, "URL:Nntp Protocol" ); keys.Add( key ); key =_newsProtocolsKey.CreateSubKey( "snews" ); key.SetValue( null, "URL:Snews Protocol" ); keys.Add( key ); for( int i = 0; i < keys.Count; ++i ) { using( key = (RegistryKey) keys[ i ] ) { key.SetValue( "EditFlags", 2 ); key.SetValue( "URL Protocol", "" ); RegistryKey subKey = key.CreateSubKey( "shell\\open\\command" ); if( subKey != null ) { subKey.SetValue( null, '"' + Application.ExecutablePath + "\" -openurl \"%1\"" ); subKey.Close(); } subKey = key.CreateSubKey( "DefaultIcon" ); if( subKey != null ) { subKey.SetValue( null, Application.ExecutablePath + ",1" ); subKey.Close(); } } } } } catch {} } private delegate void HandleNewsURLDelegate( string url, bool secure ); private static void HandleNewsURL( string url, bool secure ) { // unescape url int i; while( ( i = url.IndexOf( '%' ) ) >= 0 ) { int j = i; char c = Uri.HexUnescape( url, ref j ); url = url.Remove( i, j - i ).Insert( i, new String( c, 1 ) ); } ((Form) Core.MainWindow).Activate(); if( url.StartsWith( "//" ) ) { url = url.Substring( 2 ); int slash = url.IndexOf( '/' ); if( slash < 0 ) { HandleServer( url, true, secure ); } else { string serverName = url.Substring( 0, slash ); if( slash >= url.Length - 1 ) { HandleServer( serverName, false, secure ); } else { string rest = url.Substring( slash + 1 ); if( rest.IndexOf( "@" ) >= 0 && TryHandleArticle( ref rest ) ) { return; } IResource serverRes = HandleServer( serverName, false, secure ); if( rest.IndexOf( "@" ) >= 0 ) { HandleArticle( rest, serverRes ); } else { HandleGroup( rest, serverRes ); } } } } else { if( url.IndexOf( '@' ) >= 0 ) { HandleArticle( url, null ); } else { HandleGroup( url, null ); } } } private static IResource HandleServer( string name, bool navigate, bool secure ) { int port = secure ? 563 : 119; int portSplitter = name.IndexOf( ':' ); if( portSplitter >= 0 ) { try { port = Int32.Parse( name.Substring( portSplitter + 1 ) ); } catch {} name = name.Substring( 0, portSplitter ); } IResource server = null; IResourceList servers = Core.ResourceStore.FindResources( _newsServer, Core.Props.Name, name ); if( servers.Count > 0 ) { servers.Sort( new[] { ResourceProps.Id }, false ); server = servers[ 0 ]; } else { EditServerForm form = EditServerForm.CreateNewServerPropertiesForm( name, port ); if( form.ShowDialog( Core.MainWindow ) == DialogResult.OK ) { server = form.Servers[0]; NntpClientHelper.DeliverNewsFromServer( server ); navigate = true; } } if( server != null && navigate ) { HandleResourceSelection( server ); } return server; } private static IResource HandleGroup( string url, IResource server ) { IResourceList groups = Core.ResourceStore.FindResources( _newsGroup, Core.Props.Name, url ); IResource group = null; if( groups.Count > 0 ) { if( server == null ) { groups.Sort( new[] { ResourceProps.Id }, false ); group = groups[ 0 ]; } else { foreach( IResource res in groups ) { if( new NewsgroupResource( res ).Server == server ) { group = res; break; } } } } else { IResourceList servers = Core.ResourceStore.FindResources( _newsServer, _propNewsgroupList, url ); servers.Sort( "LastUpdated", false ); if( servers.Count > 0 ) { Core.ResourceAP.RunUniqueJob( new Subscribe2GroupDelegate( Subscribe2Group ), url, servers[ 0 ] ); groups = Core.ResourceStore.FindResources( _newsGroup, Core.Props.Name, url ); group = groups[ 0 ]; } } if( group != null ) { HandleResourceSelection( group ); } else { if( server == null ) { if( Core.State == CoreState.Running ) { MessageBox.Show( Core.MainWindow, "No such group found: " + url, "Article not found", MessageBoxButtons.OK, MessageBoxIcon.Information ); } } else { NntpConnection connection = NntpConnectionPool.GetConnection( server, "protocol handler connection" ); NntpDownloadGroupsUnit downloadGroups = new NntpDownloadGroupsUnit( server, false, JobPriority.Immediate ); downloadGroups.Finished += downloadGroups_Finished; _lastNavigatedGroup = url; connection.StartUnit( 0, downloadGroups ); } } return group; } private static void HandleGroupMisc() { HandleGroup( _lastNavigatedGroup, null ); } private static void downloadGroups_Finished( AsciiProtocolUnit unit ) { Core.UIManager.QueueUIJob( new MethodInvoker( HandleGroupMisc ) ); } private static void HandleResourceSelection( IResource resource ) { ITabManager tabManager = Core.TabManager; if( tabManager.CurrentTabId != "News" ) { tabManager.SelectResourceTypeTab( _newsGroup ); } _newsgroupsTreePane.SelectResource( resource ); } private static bool TryHandleArticle( ref string articleId ) { if( !articleId.StartsWith( "<" ) ) { articleId = '<' + articleId + '>'; } articleId = ParseTools.EscapeCaseSensitiveString( articleId ); IResource article = Core.ResourceStore.FindUniqueResource( _newsArticle, _propArticleId, articleId ); if( article != null ) { Core.UIManager.DisplayResourceInContext( article ); return true; } return false; } private static void HandleArticle( string articleId, IResource server ) { if( !TryHandleArticle( ref articleId ) ) { if( server == null ) { if( Core.State == CoreState.Running ) { MessageBox.Show( Core.MainWindow, "No such article found: " + ParseTools.UnescapeCaseSensitiveString( articleId ), "Article not found", MessageBoxButtons.OK, MessageBoxIcon.Information ); } } else { NntpConnection connection = NntpConnectionPool.GetConnection( server, "protocol handler connection" ); IResource article = Core.ResourceStore.NewResourceTransient( _newsArticle ); article.SetProp( _propArticleId, articleId ); article.AddLink( _propTo, server ); NntpDownloadArticleUnit downloadUnit = new NntpDownloadArticleUnit( article, null, JobPriority.Immediate, false ); downloadUnit.Finished += downloadUnit_Finished; connection.StartUnit( 0, downloadUnit ); } } } private static void downloadUnit_Finished( AsciiProtocolUnit unit ) { NntpDownloadArticleUnit downloadUnit = (NntpDownloadArticleUnit) unit; Core.UIManager.QueueUIJob( new StringResourceDelegate( HandleArticle ), downloadUnit.Article.GetPropText( _propArticleId ), null ); } #endregion #region IResourceDragDropHandler implementation /// /// Called to supply data in additional formats when the specified resources are being dragged. /// /// The dragged resources. /// The drag data object. public void AddResourceDragData( IResourceList dragResources, IDataObject dataObject ) { if(!dataObject.GetDataPresent( typeof(string) )) { StringBuilder sb = StringBuilderPool.Alloc(); try { foreach( IResource resource in dragResources ) { if(sb.Length != 0) sb.Append( ", " ); string text = resource.DisplayName; if(text.IndexOf( ' ' )> 0) { sb.Append( "\"" ); sb.Append( text ); sb.Append( "\"" ); } else sb.Append( text ); } dataObject.SetData( sb.ToString( ) ); } finally { StringBuilderPool.Dispose( sb ); } } } /// /// Called to return the drop effect when the specified data object is dragged over the /// specified resource. /// /// The resource over which the drag happens. /// The containing the dragged data. /// The drag-and-drop operations which are allowed by the /// originator (or source) of the drag event. /// The current state of the SHIFT, CTRL, and ALT keys, /// as well as the state of the mouse buttons. /// The target drop effect. public DragDropEffects DragOver( IResource targetResource, IDataObject data, DragDropEffects allowedEffect, int keyState ) { if( data.GetDataPresent( typeof(IResourceList) ) ) // Dragging resources over { // The resources we're dragging IResourceList dragResources = (IResourceList)data.GetData( typeof(IResourceList) ); IResource root = NewsFolders.Root; // Restrict the allowed target res-types if( targetResource.Type == _newsFolder || targetResource.Type == _newsServer ) { // Restrict dragged res-types string[] types = dragResources.GetAllTypes(); if( types.Length >= 3 ) return DragDropEffects.None; foreach( string type in types ) { if( type != _newsFolder && type != _newsGroup ) return DragDropEffects.None; } // Get the news-server of the target resource IResource targetServer = targetResource; while( targetServer != null && targetServer.Type != _newsServer ) { if( dragResources.IndexOf( targetServer ) >= 0 ) return DragDropEffects.None; targetServer = new NewsTreeNode( targetServer ).Parent; } // If there's no target server, drop's prohibited (eg Drafts) // If there is one, check that all the dragged things belong to it (dragging to another server's not allowed) if( targetServer != null ) { bool result = true; foreach( IResource dragResource in dragResources ) { IResource server = dragResource; while( server != null && server.Type != _newsServer ) { server = new NewsTreeNode( server ).Parent; } if( server == null || targetServer != server ) { result = false; break; } } return result ? DragDropEffects.Move : DragDropEffects.None; } } else if( targetResource == root ) { // Dragging into empty space / tree root: allow only those that are already direct children of the root bool bAllUnderRoot = true; foreach( IResource res in dragResources ) { IResourceList parents = res.GetLinksFrom( root.Type, Core.Props.Parent ); if(( parents.Count != 1) || ( parents[ 0 ] != root ) ) { bAllUnderRoot = false; break; } } return bAllUnderRoot ? DragDropEffects.Move : DragDropEffects.None; } } return DragDropEffects.None; } private delegate void NewsResourcesDroppedDelegate( IResource targetResource, IResourceList droppedResources ); /// /// Called to handle the drop of the specified data object on the specified resource. /// /// The drop target resource. /// The containing the dragged data. /// The drag-and-drop operations which are allowed by the /// originator (or source) of the drag event. /// The current state of the SHIFT, CTRL, and ALT keys, /// as well as the state of the mouse buttons. public void Drop( IResource targetResource, IDataObject data, DragDropEffects allowedEffect, int keyState ) { if(targetResource == NewsFolders.Root) return; if(data.GetDataPresent( typeof(IResourceList) )) { // The resources we're dragging IResourceList dragResources = (IResourceList)data.GetData( typeof(IResourceList) ); Core.ResourceAP.QueueJob( JobPriority.Immediate, "NNTP Article Dropped", new NewsResourcesDroppedDelegate( ResourcesDroppedImpl ), targetResource, dragResources ); } } private static void ResourcesDroppedImpl( IResource targetResource, IResourceList droppedResources ) { foreach( IResource dropped in droppedResources ) { new NewsTreeNode( dropped ).Parent = targetResource; } } #endregion #region implementation details public static bool IsNntpType( string type ) { return type == _newsArticle || type == _newsLocalArticle; } private void RegisterTypes() { IResourceStore store = Core.ResourceStore; IResourceTypeCollection resTypes = store.ResourceTypes; resTypes.Register( _newsServer, "Name", ResourceTypeFlags.ResourceContainer | ResourceTypeFlags.NoIndex | ResourceTypeFlags.Internal ); resTypes.Register( _newsGroup, "Newsgroup", "Name", ResourceTypeFlags.NoIndex | ResourceTypeFlags.Internal, this ); resTypes.Register( _newsArticle, "News Article", "Subject", ResourceTypeFlags.CanBeUnread, this ); resTypes.Register( _newsLocalArticle, "Local News Article", "Subject", ResourceTypeFlags.Normal, this ); resTypes[ _newsLocalArticle ].Flags = ResourceTypeFlags.Normal; resTypes.Register( _newsFolder, "News Folder", "Name", ResourceTypeFlags.ResourceContainer | ResourceTypeFlags.NoIndex | ResourceTypeFlags.Internal, this ); IPropTypeCollection propTypes = store.PropTypes; _propPort = propTypes.Register( "Port", PropDataType.Int, PropTypeFlags.Internal ); _propLastUpdated = propTypes.Register( "LastUpdated", PropDataType.Date, PropTypeFlags.Internal ); _propTo = propTypes.Register( "Newsgroups", PropDataType.Link, PropTypeFlags.DirectedLink | PropTypeFlags.CountUnread ); propTypes.RegisterDisplayName( _propTo, "Newsgroups", "Articles" ); _propRawSubject = propTypes.Register( "RawSubject", PropDataType.LongString, PropTypeFlags.Internal ); _propRawFrom = propTypes.Register( "RawFrom", PropDataType.LongString, PropTypeFlags.Internal ); _propHtmlContent = propTypes.Register( "HtmlContent", PropDataType.Blob, PropTypeFlags.Internal ); _propHasNoBody = propTypes.Register( "HasNoBody", PropDataType.Bool, PropTypeFlags.Internal ); _propArticleId = propTypes.Register( "ArticleId", PropDataType.String, PropTypeFlags.Internal ); _propReferenceId = propTypes.Register( "ReferenceId", PropDataType.String, PropTypeFlags.Internal ); _propReply = propTypes.Register( "Reply", PropDataType.Link, PropTypeFlags.DirectedLink ); propTypes.RegisterDisplayName( _propReply, "Reply To", "Replies" ); _propIsUnread = propTypes.Register( "IsUnread", PropDataType.Bool ); _propAttachment = propTypes.Register( "NewsAttachment", PropDataType.Link, PropTypeFlags.SourceLink | PropTypeFlags.DirectedLink, this ); propTypes.RegisterDisplayName( _propAttachment, "News Article", "News Attachment" ); _propEmbeddedContent = propTypes.Register( "EmbeddedContent", PropDataType.Link, PropTypeFlags.SourceLink | PropTypeFlags.DirectedLink | PropTypeFlags.Internal ); _propUsername = propTypes.Register( "Username", PropDataType.String, PropTypeFlags.Internal ); _propPassword = propTypes.Register( "Password", PropDataType.String, PropTypeFlags.Internal ); _propSsl3Enabled = propTypes.Register( "Ssl3Enabled", PropDataType.Bool, PropTypeFlags.Internal ); _propUserDisplayName = propTypes.Register( "UserDisplayName", PropDataType.String, PropTypeFlags.Internal ); _propEmailAddress = propTypes.Register( "EmailAddress", PropDataType.String ); _propNewsgroupList = propTypes.Register( "NewsgroupList", PropDataType.StringList, PropTypeFlags.Internal ); bool need2UpdateSubscribed = !propTypes.Exist( "SubscribedNewsgroupList" ); _propSubscribedNewsgroupList = propTypes.Register( "SubscribedNewsgroupList", PropDataType.StringList, PropTypeFlags.Internal ); _propNewNewsgroupList = propTypes.Register( "NewNewsgroupList", PropDataType.StringList, PropTypeFlags.Internal ); if( propTypes.Exist( "NewsgroupsHeader" ) ) { propTypes.Delete( propTypes[ "NewsgroupsHeader" ].Id ); } _propLastSelectedArticle = propTypes.Register( "LastSelectedArticle", PropDataType.Int, PropTypeFlags.Internal ); _propInlineAttachment = propTypes.Register( "InlineAttachment", PropDataType.Bool, PropTypeFlags.Internal ); _propDeletedList = propTypes.Register( "DeletedList", PropDataType.StringList, PropTypeFlags.Internal ); _propDirectory = propTypes.Register( "Directory", PropDataType.String ); CommonProps.Register(); _propNntpText = propTypes.Register( "NntpText", PropDataType.LongString, PropTypeFlags.Internal ); _propContent = propTypes.Register( "Content", PropDataType.Blob, PropTypeFlags.Internal ); _propIsIgnoredThread = propTypes.Register( "IsIgnoredThread", PropDataType.Bool, PropTypeFlags.Internal ); _propIsSelfThread = propTypes.Register( "IsSelfThread", PropDataType.Bool, PropTypeFlags.Internal ); _propThreadVisibilityToggleDate = propTypes.Register( "ThreadVisibilityToggleDate", PropDataType.Date, PropTypeFlags.Internal ); _propArticleHeaders = propTypes.Register( "MessageHeaders", PropDataType.Blob, PropTypeFlags.Internal ); _propFollowupTo = propTypes.Register( "Followup-To", PropDataType.LongString, PropTypeFlags.Internal ); _propNoMoreHeaders = propTypes.Register( "NoMoreHeaders", PropDataType.Bool, PropTypeFlags.Internal ); _propFirstArticle = propTypes.Register( "FirstArticle", PropDataType.Int, PropTypeFlags.Internal ); _propLastArticle = propTypes.Register( "LastArticle", PropDataType.Int, PropTypeFlags.Internal ); _propArticleNumbers = propTypes.Register( "ArticleNumbers", PropDataType.LongString, PropTypeFlags.Internal ); _propLastArticleDate = propTypes.Register( "ThreadLastArticleDate", PropDataType.Date, PropTypeFlags.Normal, this ); propTypes.RegisterDisplayName( _propLastArticleDate, "Thread Update" ); _propAbbreviateLevel = propTypes.Register( "AbbreviateLevel", PropDataType.Int, PropTypeFlags.Internal ); if( propTypes.Exist( "DeliverOnStartup" ) && propTypes[ "DeliverOnStartup" ].DataType == PropDataType.Bool ) { propTypes.Delete( propTypes[ "DeliverOnStartup" ].Id ); } _propCountToDownloadAtTime = propTypes.Register( "CountToDownloadAtTime", PropDataType.Int, PropTypeFlags.Internal ); _propDeliverOnStartup = propTypes.Register( "DeliverOnStartup", PropDataType.Int, PropTypeFlags.Internal ); _propDeliverFreq = propTypes.Register( "DeliverFreq", PropDataType.Int, PropTypeFlags.Internal ); _propMarkFromMeAsRead = propTypes.Register( "MarkFromMeAsRead", PropDataType.Int, PropTypeFlags.Internal ); if( propTypes.Exist( "DownloadBodiesOnDeliver" ) && propTypes[ "DownloadBodiesOnDeliver" ].DataType == PropDataType.Bool ) { propTypes.Delete( propTypes[ "DownloadBodiesOnDeliver" ].Id ); } _propDownloadBodiesOnDeliver = propTypes.Register( "DownloadBodiesOnDeliver", PropDataType.Int, PropTypeFlags.Internal ); _propDownloadBodyOnSelection = propTypes.Register( "DownloadBodyOnSelection", PropDataType.Int, PropTypeFlags.Internal ); if( propTypes.Exist( "ForceDownload" ) ) { propTypes.Delete( propTypes[ "ForceDownload" ].Id ); } _propMailFormat = propTypes.Register( "MailFormat", PropDataType.LongString, PropTypeFlags.Internal ); _propMIMETextEncoding = propTypes.Register( "MIMETextEncoding", PropDataType.LongString, PropTypeFlags.Internal ); if( propTypes.Exist( "Allow8BitHeaders" ) ) { propTypes.Delete( propTypes[ "Allow8BitHeaders" ].Id ); } _propPutInOutbox = propTypes.Register( "PutInOutbox", PropDataType.Bool, PropTypeFlags.Internal ); _propOverrideSigSettings = propTypes.Register( "OverrideSigSettings", PropDataType.Bool, PropTypeFlags.Internal ); _propUseSignature = propTypes.Register( "UseSignature", PropDataType.Int, PropTypeFlags.Internal ); _propMailSignature = propTypes.Register( "MailSignature", PropDataType.LongString, PropTypeFlags.Internal ); _propReplySignaturePosition = propTypes.Register( "ReplySignaturePosition", PropDataType.Int, PropTypeFlags.Internal ); _propNewsSortOrder = propTypes.Register( "NewsSortOrder", PropDataType.Int, PropTypeFlags.Internal ); _propMarkReadOnExit = propTypes.Register( "MarkReadOnExit", PropDataType.Bool, PropTypeFlags.Internal ); _propMarkReadOnLeave = propTypes.Register( "MarkReadOnLeave", PropDataType.Bool, PropTypeFlags.Internal ); store.RegisterLinkRestriction( _newsArticle, Core.ContactManager.Props.LinkFrom, "Contact", 0, 1 ); store.RegisterLinkRestriction( _newsLocalArticle, Core.ContactManager.Props.LinkFrom, "Contact", 0, 1 ); store.RegisterLinkRestriction( _newsGroup, _propTo, _newsArticle, 0, Int32.MaxValue ); store.RegisterUniqueRestriction( _newsArticle, _propArticleId ); RemoveHasBodyProperty(); if( need2UpdateSubscribed ) { UpdateSubscribedGroups(); } MoveDeletedListFromGroupsToServer(); UpdateDisplayThreaded(); UpdateNewsSortOrder(); UpdateRepliesToMyPostsLogic(); UpdateArticleHeaders(); UpdateArticleHtmlBodies(); } private static void RegisterDisplayColumns() { ImageListColumn attachmentColumn = new ImageListColumn( -_propAttachment ); attachmentColumn.SetHeaderIcon( LoadNewsIcon( "AttachmentHeader.ico" ) ); attachmentColumn.SetAnyValueIcon( LoadNewsIcon( "AttachmentColumn.ico" ) ); Core.DisplayColumnManager.RegisterCustomColumn( -_propAttachment, attachmentColumn ); } private static void RemoveHasBodyProperty() { IResourceStore store = Core.ResourceStore; if( store.PropTypes.Exist( "HasBody" ) ) { IResourceList emptyArticles = store.GetAllResources( _newsArticle ).Minus( store.FindResourcesWithProp( null, "HasBody" ) ); foreach( IResource artcile in emptyArticles ) { artcile.SetProp( _propHasNoBody, true ); } store.PropTypes.Delete( store.PropTypes[ "HasBody" ].Id ); } } private static void UpdateSubscribedGroups() { IResourceStore store = Core.ResourceStore; if( store.PropTypes.Exist( "RfcViolatingServer" ) ) { store.PropTypes.Delete( store.PropTypes[ "RfcViolatingServer" ].Id ); } if( store.PropTypes.Exist( "XoverGroup" ) ) { store.PropTypes.Delete( store.PropTypes[ "XoverGroup" ].Id ); } if( store.PropTypes.Exist( "ArticleNumber" ) ) { store.PropTypes.Delete( store.PropTypes[ "ArticleNumber" ].Id ); } IResourceList groups; IResourceList servers; groups = store.GetAllResources( _newsGroup ); foreach( IResource group in groups ) { servers = group.GetLinksFrom( _newsServer, Core.Props.Parent ); if( servers.Count == 0 ) { UnsubscribeAction.Unsubscribe( group, true ); } else { string groupName = group.GetPropText( Core.Props.Name ); new NewsTreeNode( group ).Parent = servers[ 0 ]; IResourceList articles = group.GetLinksTo( null, _propTo ); for( int i = 1; i < servers.Count; ++i ) { IResource newGroup = Core.ResourceStore.BeginNewResource( _newsGroup ); newGroup.SetProp( Core.Props.Name, groupName ); new NewsTreeNode( newGroup ).Parent = servers[ i ]; newGroup.EndUpdate(); foreach( IResource article in articles ) { article.AddLink( _propTo, newGroup ); } } } } servers = store.GetAllResources( _newsServer ); foreach( IResource server in servers ) { IStringList subscriptions = server.GetStringListProp( _propSubscribedNewsgroupList ); subscriptions.Clear(); groups = new ServerResource( server ).Groups; foreach( IResource group in groups ) { string groupName = group.GetPropText( Core.Props.Name ); if( subscriptions.IndexOf( groupName ) < 0 ) { subscriptions.Add( groupName ); } } subscriptions.Dispose(); } } private static void MoveDeletedListFromGroupsToServer() { IResourceList groups = Core.ResourceStore.FindResourcesWithProp( _newsGroup, _propDeletedList ); if( groups.Count > 0 ) { foreach( IResource group in groups ) { IResource server = new NewsgroupResource( group ).Server; if( server != null ) { IStringList serverIds = server.GetStringListProp( _propDeletedList ); IStringList groupIds = group.GetStringListProp( _propDeletedList ); foreach( string id in groupIds ) { if( serverIds.IndexOf( id ) < 0 ) { serverIds.Add( id ); } } group.DeleteProp( _propDeletedList ); serverIds.Dispose(); } } } } private static void UpdateDisplayThreaded() { IResourceStore store = Core.ResourceStore; IPropTypeCollection types = store.PropTypes; if( types.Exist( "DisplayPlainGroup" ) ) { IResourceList threadedResources = store.GetAllResources( new[] { _newsGroup, _newsFolder, _newsServer } ).Minus( store.FindResourcesWithProp( null, "DisplayPlainGroup" ) ); foreach( IResource threaded in threadedResources ) { threaded.SetProp( Core.Props.DisplayThreaded, true ); } types.Delete( types[ "DisplayPlainGroup" ].Id ); } } private static void UpdateNewsSortOrder() { if( !ObjectStore.ReadBool( "NNTP", "UpdateNewsSortOrder3", false ) ) { foreach( IResource folder in Core.ResourceStore.GetAllResources( _newsFolder ) ) { if( NewsFolders.IsDefaultFolder( folder ) ) { folder.SetProp( _propNewsSortOrder, Int32.MaxValue ); } else { folder.DeleteProp( _propNewsSortOrder ); } } foreach( IResource server in Core.ResourceStore.GetAllResources( _newsServer ) ) { ServerResource serverResource = new ServerResource( server ); foreach( IResource group in serverResource.Groups ) { if( new NewsgroupResource( group ).IsSubscribed ) { group.SetProp( _propNewsSortOrder, 0 ); } else { group.SetProp( _propNewsSortOrder, 1 ); } } } ObjectStore.WriteBool( "NNTP", "UpdateNewsSortOrder3", true ); } } private static void UpdateRepliesToMyPostsLogic() { if( !ObjectStore.ReadBool( "NNTP", "UpdateRepliesToMyPosts", false ) ) { if( Core.ProgressWindow != null ) { Core.ProgressWindow.UpdateProgress( 0, "Upgrading News Articles database", null ); } IntHashSet CollectedIds = new IntHashSet(); IntHashSet resultSet = new IntHashSet(); IResourceList allMyHeads = Core.ContactManager.MySelf.Resource.GetLinksOfType( _newsArticle, "From" ).Intersect( Core.ResourceStore.FindResourcesWithProp( _newsArticle, -Core.Props.Reply ), true ); foreach( IResource res in allMyHeads ) CollectedIds.Add( res.Id ); ProcessResources( resultSet, CollectedIds ); if( Core.ProgressWindow != null ) { Core.ProgressWindow.UpdateProgress( 0, "", null ); } ObjectStore.WriteBool( "NNTP", "UpdateRepliesToMyPosts", true ); } } private static void UpdateArticleHeaders() { IProgressWindow pw = Core.ProgressWindow; if( Core.ResourceStore.PropTypes.Exist( "ArticleHeaders" ) ) { int obsoleteId = Core.ResourceStore.PropTypes[ "ArticleHeaders" ].Id; IResourceList articles = Core.ResourceStore.GetAllResources( _newsArticle ); int percent = 0, lastPercent = -1, i = 0; int count = articles.Count; foreach( IResource article in articles ) { if( pw != null && lastPercent != percent ) { lastPercent = percent; pw.UpdateProgress( percent, "Updating news articles' headers...", null ); } if( article.HasProp( obsoleteId ) ) { article.SetProp( _propArticleHeaders, article.GetPropText( obsoleteId ) ); } percent = ++i * 100 / count; } Core.ResourceStore.PropTypes.Delete( obsoleteId ); } } private static void UpdateArticleHtmlBodies() { IProgressWindow pw = Core.ProgressWindow; if( Core.ResourceStore.PropTypes.Exist( "HtmlBody" ) ) { int obsoleteId = Core.ResourceStore.PropTypes[ "HtmlBody" ].Id; IResourceList articles = Core.ResourceStore.GetAllResources( _newsArticle ); int percent = 0, lastPercent = -1, i = 0; int count = articles.Count; foreach( IResource article in articles ) { if( pw != null && lastPercent != percent ) { lastPercent = percent; pw.UpdateProgress( percent, "Updating news articles' HTML content...", null ); } if( article.HasProp( obsoleteId ) ) { article.SetProp( _propHtmlContent, article.GetPropText( obsoleteId ) ); } percent = ++i * 100 / count; } Core.ResourceStore.PropTypes.Delete( obsoleteId ); } } private static void ProcessResources( IntHashSet result, IntHashSet source ) { IntHashSet temp = new IntHashSet(); foreach( IntHashSet.Entry e in source ) { IResource res = Core.ResourceStore.LoadResource( e.Key ); res.SetProp( _propIsSelfThread, true ); IResourceList children = res.GetLinksTo( _newsArticle, Core.Props.Reply ); for( int i = 0; i < children.Count; i++ ) { int child = children[ i ].Id; if( !source.Contains( child ) && !result.Contains( child ) ) temp.Add( child ); } } foreach( IntHashSet.Entry e in temp ) result.Add( e.Key ); if( temp.Count > 0 ) ProcessResources( result, temp ); } private void resourceBrowser_ContentChanged( object sender, EventArgs e ) { IResource selected = _lastSelected; if( selected != null && !selected.IsDeleted && selected != Core.ResourceBrowser.OwnerResource && selected.HasProp( _propMarkReadOnLeave ) && ( selected.Type == _newsGroup || selected.Type == _newsFolder || selected.Type == _newsServer ) ) { MarkAllAsReadAction.MarkGroupsRead( selected.ToResourceList() ); } _lastSelected = Core.ResourceBrowser.OwnerResource; } private static void Core_StateChanged( object sender, EventArgs e ) { if( Core.State == CoreState.Running ) { IResourceList servers = Core.ResourceStore.GetAllResources( _newsServer ); foreach( IResource server in servers ) { ServerResource serverResource = new ServerResource( server ); if( serverResource.DeliverOnStartup ) { NntpClientHelper.DeliverNewsFromServer( server ); } else { int freq = serverResource.DeliverFreq; if( freq > 0 ) { Core.NetworkAP.QueueJobAt( serverResource.LastUpdateTime.AddMinutes( freq ), "Deliver News", new ResourceDelegate( NntpClientHelper.DeliverNewsFromServer ), server ); } } } } else if( Core.State == CoreState.ShuttingDown ) { MarkAllAsReadAction.MarkGroupsRead( Core.ResourceStore.FindResourcesWithProp( null, _propMarkReadOnExit ) ); } } /** * collect articles for a view */ internal static IResourceList CollectArticles( IResource res, bool filterLocalArticles ) { IResourceList articles; if( res.Type == _newsGroup ) { articles = res.GetLinksToLive( null, _propTo ); } else if( NewsFolders.IsDefaultFolder( res ) ) { articles = res.GetLinksToLive( null, _propTo ); filterLocalArticles = false; // forcedly don't minus local articles from default folder's view } else { IResourceList groups = new NewsTreeNode( res ).Groups; articles = Core.ResourceStore.EmptyResourceList; foreach( IResource group in groups ) { articles = articles.Union( CollectArticles( group, false ), true ); } } if( filterLocalArticles ) { articles = articles.Minus( Core.ResourceStore.GetAllResources( _newsLocalArticle ) ); } return articles; } internal delegate bool Subscribe2GroupDelegate( string group, IResource server ); /** * return true if group was actually subscribed */ internal static bool Subscribe2Group( string group, IResource server ) { IResourceStore store = Core.ResourceStore; IResource groupRes = null; IResourceList groups = store.FindResources( _newsGroup, Core.Props.Name, group ); foreach( IResource groupResource in groups ) { if( new NewsgroupResource( groupResource ).Server == server ) { groupRes = groupResource; groupRes.BeginUpdate(); break; } } if( groupRes == null ) { IResourceList fakeGroups = Core.ResourceStore.FindResources( _newsGroup, Core.Props.Name, group ); foreach( IResource fakeGroup in fakeGroups ) { if( !fakeGroup.HasProp( Core.Props.Parent ) ) { groupRes = fakeGroup; groupRes.BeginUpdate(); break; } } if( groupRes == null ) { groupRes = store.BeginNewResource( _newsGroup ); } new NewsTreeNode( groupRes ).Parent = server; groupRes.SetProp( Core.Props.DisplayThreaded, server.GetProp( Core.Props.DisplayThreaded ) ); } bool subscriptionPerformed; try { ServerResource serverResource = new ServerResource( server ); subscriptionPerformed = !( new NewsgroupResource( groupRes ).IsSubscribed ); serverResource.SubscribeToGroup( group ); groupRes.SetProp( Core.Props.Name, group ); new NewsgroupResource( groupRes ).InvalidateDisplayName( serverResource.AbbreviateLevel ); IResourceList categories = Core.CategoryManager.GetResourceCategories( server ); foreach( IResource category in categories ) { Core.CategoryManager.AddResourceCategory( groupRes, category ); } if( subscriptionPerformed ) { Core.WorkspaceManager.AddToActiveWorkspace( groupRes ); _newsgroupsTreePane.ExpandParents( groupRes ); } } finally { groupRes.EndUpdate(); } if( subscriptionPerformed ) { NntpClientHelper.DownloadHeadersFromGroup( groupRes ); } CheckGroups(); return subscriptionPerformed; } internal static void DeliverNews( bool invokedByUser ) { IUIManager uiMgr = Core.UIManager; IResourceList servers = Core.ResourceStore.GetAllResources( _newsServer ); if( servers.Count > 0 ) { IResource outbox = NewsFolders.Outbox; IResourceList localArticles = outbox.GetLinksTo( _newsLocalArticle, _propTo ).Minus( Core.ResourceStore.FindResourcesWithProp( null, Core.Props.IsDeleted ) ); if( localArticles.Count > 0 ) { uiMgr.GetStatusWriter( typeof( NntpPlugin ), StatusPane.Network ).ShowStatus( "Posting articles from Outbox" ); foreach( IResource article in localArticles ) { NntpClientHelper.PostArticle( article, null, false ); } } uiMgr.GetStatusWriter( typeof( NntpPlugin ), StatusPane.Network ).ShowStatus( "Delivering news..." ); _deliverNewsUnitCount = 0; IResource preferableGroup = ( Core.TabManager.CurrentTab.Id == "News" ) ? _newsgroupsTreePane.SelectedNode : null; IResource preferableServer = ( preferableGroup != null && preferableGroup.Type == _newsGroup ) ? new NewsgroupResource( preferableGroup ).Server : null; if( preferableServer != null ) { NntpClientHelper.DeliverNewsFromServer( preferableServer, preferableGroup, invokedByUser, deliverNewsFromServer_Finished ); } foreach( IResource server in servers ) { if( preferableServer != server ) { NntpClientHelper.DeliverNewsFromServer( server, preferableGroup, invokedByUser, deliverNewsFromServer_Finished ); } } } } private static void deliverNewsFromServer_Finished( AsciiProtocolUnit unit ) { if( _deliverNewsUnitCount == 0 ) { Core.UIManager.GetStatusWriter( typeof( NntpPlugin ), StatusPane.Network ).ClearStatus(); } } internal static Icon LoadNewsIcon( string iconName ) { Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream( "NntpPlugin.Icons." + iconName ); if( stream != null ) { return new Icon( stream ); } return null; } internal static void CheckGroups() { _areThereGroups = Core.ResourceStore.GetAllResources( _newsGroup ).Count > 0; } private static string DisplayGroupOrServerError( IResource res ) { return res.GetPropText( Core.Props.LastError ); } #region Icon Providers /** * icon provider for newsgroup resources */ private class NewsgroupIconProvider: IResourceIconProvider, IOverlayIconProvider { private Icon _groupMainIcon; private Icon _groupUnsubscribedIcon; private Icon _groupErrorIcon; private readonly Icon[] _pausedSign = new Icon[ 1 ]; public NewsgroupIconProvider() { _pausedSign[ 0 ] = LoadNewsIcon( "pause.ico" ); } private Icon GroupMainIcon { get { if( _groupMainIcon == null ) { _groupMainIcon = LoadNewsIcon( "newsgroups.ico" ); } return _groupMainIcon; } } private Icon GroupUnsubscribedIcon { get { if( _groupUnsubscribedIcon == null ) { _groupUnsubscribedIcon = LoadNewsIcon( "unsubscribed_newsgroups.ico" ); } return _groupUnsubscribedIcon; } } private Icon GroupErrorIcon { get { if( _groupErrorIcon == null ) { _groupErrorIcon = LoadNewsIcon( "error_newsgroups.ico" ); } return _groupErrorIcon; } } public Icon GetResourceIcon( IResource resource ) { if( resource.Type == _newsGroup ) { if( resource.HasProp( Core.Props.LastError ) ) { return GroupErrorIcon; } return ( new NewsgroupResource( resource ).IsSubscribed ) ? GroupMainIcon : GroupUnsubscribedIcon; } return null; } public Icon GetDefaultIcon( string resType ) { if( resType == _newsGroup ) { return GroupMainIcon; } return null; } public Icon[] GetOverlayIcons( IResource res ) { int order = res.GetIntProp( _propNewsSortOrder ); return (order == 1) ? _pausedSign : null; } } /// /// Decorate a news article with "Paused" sign if it is the head of /// the thread which was paused for updates. /// private class ArticleOverlayIconProvider: IOverlayIconProvider { private readonly Icon[] _pausedSign = new Icon[ 1 ]; public ArticleOverlayIconProvider() { _pausedSign[ 0 ] = LoadNewsIcon( "pause.ico" ); } public Icon[] GetOverlayIcons( IResource res ) { return (res.HasProp( _propIsIgnoredThread) ? _pausedSign : null); } } #endregion Icon Providers private class NewsWorkspaceSelectorFilter: IResourceNodeFilter { public bool AcceptNode( IResource res, int level ) { return !NewsFolders.IsDefaultFolder( res ); } } private class NewsArticleDeleter: DefaultResourceDeleter { public override bool CanIgnoreRecyclebin() { return false; } public override void DeleteResourcePermanent( IResource article ) { string id = article.GetPropText( _propArticleId ); if( id.Length > 0 ) { IResourceList groups = article.GetLinksOfType( _newsGroup, _propTo ); HashSet servers = new HashSet(); foreach( IResource group in groups ) { IResource server = new NewsgroupResource( group ).Server; if( server != null ) { servers.Add( server ); } } foreach( HashSet.Entry e in servers ) { IResource server = (IResource) e.Key; if( !Core.ResourceStore.FindResources( null, _propDeletedList, id ).Contains( server ) ) { server.GetStringListProp( _propDeletedList ).Add( id ); } } } IContactManager contactManager = Core.ContactManager; IResource from = article.GetLinkProp( contactManager.Props.LinkFrom ); article.GetLinksOfType( null, _propAttachment ).DeleteAll(); article.Delete(); if( from != null ) { contactManager.DeleteUnusedContacts( from.ToResourceList() ); } } } internal const string _newsServer = "NewsServer"; internal const string _newsGroup = "NewsGroup"; internal const string _newsArticle = "Article"; internal const string _newsFolder = "NewsFolder"; internal const string _newsLocalArticle = "LocalArticle"; internal const string _unknownFileResourceType = "UnknownFile"; internal const string _eapGroupName = "jetbrains.omniamea.eap"; internal const string _eapAnnouncementGroupName = "jetbrains.omniamea.eap.announcements"; internal static NntpPlugin _plugin; internal static int _propPort; internal static int _propLastUpdated; internal static int _propTo; internal static int _propRawSubject; internal static int _propRawFrom; internal static int _propHtmlContent; internal static int _propHasNoBody; internal static int _propArticleId; internal static int _propReferenceId; internal static int _propReply; internal static int _propIsUnread; internal static int _propAttachment; internal static int _propEmbeddedContent; internal static int _propUsername; internal static int _propPassword; internal static int _propSsl3Enabled; internal static int _propUserDisplayName; internal static int _propEmailAddress; internal static int _propNewsgroupList; internal static int _propSubscribedNewsgroupList; internal static int _propNewNewsgroupList; internal static int _propLastSelectedArticle; internal static int _propLastArticleDate; internal static int _propInlineAttachment; internal static int _propDeletedList; internal static int _propDirectory; internal static int _propNntpText; internal static int _propContent; internal static int _propIsIgnoredThread; internal static int _propIsSelfThread; internal static int _propThreadVisibilityToggleDate; internal static int _propArticleHeaders; internal static int _propFollowupTo; internal static int _propNoMoreHeaders; internal static int _propFirstArticle; internal static int _propLastArticle; internal static int _propArticleNumbers; internal static int _propAbbreviateLevel; internal static int _propCountToDownloadAtTime; internal static int _propDeliverOnStartup; internal static int _propDeliverFreq; internal static int _propMarkFromMeAsRead; internal static int _propDownloadBodiesOnDeliver; internal static int _propDownloadBodyOnSelection; internal static int _propMailFormat; internal static int _propMIMETextEncoding; internal static int _propPutInOutbox; internal static int _propOverrideSigSettings; internal static int _propUseSignature; internal static int _propMailSignature; internal static int _propReplySignaturePosition; internal static int _propNewsSortOrder; internal static int _propMarkReadOnExit; internal static int _propMarkReadOnLeave; internal static ProductType _productType; internal static IResourceTreePane _newsgroupsTreePane; internal static bool _areThereGroups; internal static int _deliverNewsUnitCount; internal static ArticlePreviewPane _previewPane; private IResource _lastSelected; private static string _lastNavigatedGroup; private static RegistryKey _newsProtocolsKey; public static readonly string _networkUnavailable = "Network is unavailable"; #endregion } }