/// /// 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.IO; using System.Runtime.InteropServices; using System.Text; using System.Windows.Forms; using JetBrains.Omea.Base; using JetBrains.Omea.Conversations; using JetBrains.Omea.FiltersManagement; using JetBrains.Omea.GUIControls; using JetBrains.Omea.Net; using JetBrains.Omea.OpenAPI; using JetBrains.Omea.FileTypes; namespace JetBrains.Omea.Nntp { /** * display subscription dialog */ public class ManageNewsgroupsAction: IAction { public void Execute( IActionContext context ) { IResourceList resources = context.SelectedResources; if( resources.Count == 1 ) { IResource selected = resources[ 0 ]; if( selected.Type == NntpPlugin._newsServer ) { SubscribeForm.SubscribeToGroups( selected ); return; } if( selected.Type == NntpPlugin._newsGroup ) { SubscribeForm.SubscribeToGroups( new NewsgroupResource( selected ).Server ); return; } } SubscribeForm.SubscribeToGroups(); } public void Update( IActionContext context, ref ActionPresentation presentation ) { try { if ( context.Kind == ActionContextKind.MainMenu || context.Kind == ActionContextKind.Toolbar ) { return; } if ( context.SelectedResources.Count == 0 ) { if ( context.Instance != NntpPlugin._newsgroupsTreePane ) { presentation.Visible = false; } return; } string[] types = context.SelectedResources.GetAllTypes(); if( types.Length > 2 ) { presentation.Visible = false; return; } foreach( string type in types ) { if( type != NntpPlugin._newsGroup && type != NntpPlugin._newsServer ) { presentation.Visible = false; break; } } } finally { UpdateActionIfNetworkUnavailable.Update( ref presentation ); } } } /** * display server properties dialog */ public class ServerPropertiesAction: SimpleAction { public override void Execute( IActionContext context ) { if ( context.SelectedResources.Count == 0 ) return; EditServerForm form = EditServerForm.CreateServerPropertiesForm( context.SelectedResources ); using( form ) { if( form.ShowDialog( Core.MainWindow ) == DialogResult.OK ) { foreach ( IResource server in context.SelectedResources ) { NntpConnectionPool.CloseConnections( server ); NntpClientHelper.DeliverNewsFromServer( server, null, true, null ); } } } } } /** * displays posting form & posts an article to selected newsgroups */ public class PostAction: SimpleAction { public override void Execute( IActionContext context ) { IResourceList groups = null; IResourceList resources = context.SelectedResources; if( resources.AllResourcesOfType( NntpPlugin._newsGroup ) ) { groups = resources; } else { IResource owner = Core.ResourceBrowser.OwnerResource; if( owner != null ) { string type = owner.Type; if( type == NntpPlugin._newsGroup ) { groups = owner.ToResourceListLive(); } else if( type == NntpPlugin._newsFolder ) { groups = new NewsTreeNode( owner ).Groups; } } } EditMessageForm.EditAndPostMessage( groups, string.Empty, string.Empty, string.Empty, true ); } public override void Update( IActionContext context, ref ActionPresentation presentation ) { presentation.Enabled = NntpPlugin._areThereGroups; } } /** * initiates delivering news */ public class DeliverNewsAction: SimpleAction { public override void Execute( IActionContext context ) { NntpPlugin.DeliverNews( true ); } public override void Update( IActionContext context, ref ActionPresentation presentation ) { presentation.Enabled = NntpPlugin._areThereGroups; UpdateActionIfNetworkUnavailable.Update( ref presentation ); } } /** * initiates delivering news from a server */ public class DeliverNewsFromServerAction : ActionOnResource { private IResourceList _servers = Core.ResourceStore.EmptyResourceList; public override void Execute( IActionContext context ) { foreach( IResource server in _servers ) { NntpClientHelper.DeliverNewsFromServer( server, NntpPlugin._newsgroupsTreePane.SelectedNode, true, null ); } _servers = Core.ResourceStore.EmptyResourceList; } public override void Update( IActionContext context, ref ActionPresentation presentation ) { base.Update( context, ref presentation ); if( presentation.Visible ) { presentation.Enabled = false; IResourceList selected = context.SelectedResources; if( selected.AllResourcesOfType( NntpPlugin._newsServer ) ) { presentation.Enabled = true; _servers = selected; } else { IResource server = Core.ResourceBrowser.OwnerResource; while( server != null ) { if( server.Type == NntpPlugin._newsServer ) { presentation.Enabled = true; _servers = server.ToResourceList(); break; } server = new NewsTreeNode( server ).Parent; } } UpdateActionIfNetworkUnavailable.Update( ref presentation ); } } } /** * creates formatted reply message & displays posting form */ public class ReplyAction: IAction { private readonly EditMessageForm _form; public ReplyAction() {} internal ReplyAction( EditMessageForm form ) { _form = form; } public void Execute( IActionContext context ) { IResource article = context.SelectedResources[ 0 ]; if( Settings.MarkAsReadOnReplyAndFormard && article.HasProp( NntpPlugin._propIsUnread ) ) { new ResourceProxy( article ).SetProp( NntpPlugin._propIsUnread, false ); } string origSubject = article.GetPropText( Core.Props.Subject ); if( !origSubject.ToLower().StartsWith( "re:" ) ) { origSubject = "Re: " + origSubject; } IResourceList groupList = article.GetLinksFromLive( NntpPlugin._newsGroup, NntpPlugin._propTo ); IResource groupRes = null; lock( groupList ) { foreach(IResource resource in groupList) { if (new NewsgroupResource(resource).Server != null) { groupRes = resource; break; } } } QuoteSettings settings = QuoteSettings.Default; if( groupRes != null ) { ServerResource server = new ServerResource( new NewsgroupResource( groupRes ).Server ); settings.UseSignature = server.UseSignature; settings.SignatureInReplies = server.ReplySignaturePosition; settings.Signature = server.MailSignature; } string quote = GetQuotedText( article, context, settings ); if( article.HasProp( NntpPlugin._propFollowupTo ) ) { string followUpTo = article.GetPropText( NntpPlugin._propFollowupTo ); string[] groups = followUpTo.Split( ',' ); groupList = Core.ResourceStore.EmptyResourceList; foreach( string group in groups ) { IResourceList groupResources = Core.ResourceStore.FindResources( NntpPlugin._newsGroup, Core.Props.Name, group.Trim() ); if( groupResources.Count > 0 ) { groupRes = groupResources[ 0 ]; if( groupRes != null ) { groupList = groupList.Union( groupRes.ToResourceListLive() ); } } } } if( _form != null && Settings.CloseOnReply ) { _form.Close(); } EditMessageForm.EditAndPostMessage( groupList, origSubject, quote, article.GetPropText( NntpPlugin._propArticleId ), false ); } public void Update(IActionContext context, ref ActionPresentation presentation) { if(context.SelectedResources.Count == 1) { // There's exactly one resource selected; check its type // Ensure it's not a local article as we cannot reply to a local article presentation.Enabled = presentation.Visible = (context.SelectedResources[0].Type == NntpPlugin._newsArticle); } else { presentation.Visible = false; // Can be applied when there is exactly one resource } } internal virtual string GetQuotedText( IResource article, IActionContext context, QuoteSettings settings ) { string selected = context.SelectedPlainText; if( String.IsNullOrEmpty( selected ) ) return Core.MessageFormatter.QuoteMessage( article, Core.Props.LongBody, settings ); else return Core.MessageFormatter.QuoteMessage( article, selected, settings ); } } /** * creates formatted reply message & displays posting form */ public class Reply2SenderWithoutQuotation : ReplyAction { internal override string GetQuotedText( IResource article, IActionContext context, QuoteSettings settings ) { return Core.MessageFormatter.QuoteMessage( article, string.Empty, settings ); } } /** * opens news attachment in correspondent application */ public class OpenAttachmentAction : ActionOnSingleResource { public override void Execute( IActionContext context ) { Core.FileResourceManager.OpenSourceFile( context.SelectedResources[ 0 ] ); } public override void Update( IActionContext context, ref ActionPresentation presentation ) { base.Update( context, ref presentation ); if( presentation.Visible ) { IResource article = context.SelectedResources[ 0 ].GetLinkProp( NntpPlugin._propAttachment ); if( article == null || ( article.Type != NntpPlugin._newsArticle && article.Type != NntpPlugin._newsLocalArticle ) ) { presentation.Visible = false; } } } } /** * saves news attachment on disk */ public class SaveAttachmentAction: ActionOnResource { public override void Execute( IActionContext context ) { IResource attachment = context.SelectedResources[ 0 ]; string pathToSave = string.Empty; bool mayMissDialog = (Control.ModifierKeys == Keys.Control); if( mayMissDialog ) pathToSave = Core.SettingStore.ReadString( "Omea", "LastUsedPath" ); mayMissDialog = mayMissDialog && (pathToSave.Length > 0); if( pathToSave.Length == 0 ) pathToSave = Environment.CurrentDirectory; SaveFileDialog saveDialog = new SaveFileDialog(); saveDialog.FileName = attachment.GetPropText( Core.Props.Name ).Replace( '\\', '-' ).Replace( '/', '-' ).Replace( ':','-' ); saveDialog.InitialDirectory = pathToSave; DialogResult dialogResult; if( mayMissDialog ) dialogResult = DialogResult.OK; else { try { dialogResult = saveDialog.ShowDialog( Core.MainWindow ); } catch( Exception e ) { Utils.DisplayException( e, "Can't save attachment" ); dialogResult = DialogResult.Cancel; } } if( dialogResult == DialogResult.OK ) { Stream fileStream; if( ( fileStream = saveDialog.OpenFile() ) != null ) { try { Stream blob = attachment.GetBlobProp( NntpPlugin._propContent ); blob.Position = 0; FileResourceManager.CopyStream( blob, fileStream ); } catch( Exception e ) { Utils.DisplayException( e, "Can't save attachment" ); } finally { fileStream.Close(); } } } Core.SettingStore.WriteString( "Omea", "LastUsedPath", pathToSave ); } public override void Update( IActionContext context, ref ActionPresentation presentation ) { base.Update( context, ref presentation ); if( presentation.Visible ) { IResource article = context.SelectedResources[ 0 ].GetLinkProp( NntpPlugin._propAttachment ); if( article == null || ( article.Type != NntpPlugin._newsArticle && article.Type != NntpPlugin._newsLocalArticle ) ) { presentation.Visible = false; } } } } /** * Base class for read headers actions */ public abstract class ReadHeadersBaseAction : ActionOnResource { public override void Update( IActionContext context, ref ActionPresentation presentation ) { base.Update( context, ref presentation ); if( presentation.Visible && presentation.Enabled ) { bool enabled = false; IResourceList resources = context.SelectedResources; foreach( IResource group in resources ) { NewsgroupResource groupResource = new NewsgroupResource( group ); enabled = enabled || ( groupResource.IsSubscribed && !group.HasProp( NntpPlugin._propNoMoreHeaders ) ); if( enabled ) { break; } } presentation.Enabled = enabled; UpdateActionIfNetworkUnavailable.Update( ref presentation ); } } } /** * Reads next N headers from a newsgroup * N is got from the Setting Store */ public class ReadNextHeaders: ReadHeadersBaseAction { public override void Execute( IActionContext context ) { IResourceList resources = context.SelectedResources; foreach( IResource group in resources ) { NntpClientHelper.DownloadNextHeadersFromGroup( group ); } } public override void Update( IActionContext context, ref ActionPresentation presentation ) { base.Update( context, ref presentation ); if( presentation.Visible && presentation.Enabled ) { int nextHeaders = 0; IResourceList resources = context.SelectedResources; foreach( IResource group in resources ) { NewsgroupResource groupResource = new NewsgroupResource( group ); if( nextHeaders == 0 ) { nextHeaders = groupResource.CountToDownloadAtTime; } else if( nextHeaders != groupResource.CountToDownloadAtTime ) { nextHeaders = 0; break; } } presentation.Text = "Download Next " + ( ( nextHeaders != 0 ) ? nextHeaders.ToString() : "" ) + " Headers"; } } } /** * Reads all headers for a newsgroup */ public class ReadAllHeaders: ReadHeadersBaseAction { public override void Execute( IActionContext context ) { IResourceList resources = context.SelectedResources; if( resources.Count == 1 ) { if( MessageBox.Show( Core.MainWindow, "All headers of the news articles which exist in this newsgroup will be downloaded.\r\nThis may require some time. Do you wish to continue?", "Download All Headers", MessageBoxButtons.YesNo, MessageBoxIcon.Question ) != DialogResult.Yes ) { return; } } else { if( MessageBox.Show( Core.MainWindow, "All headers of the news articles which exist in these newsgroups will be downloaded.\r\nThis may require some time. Do you wish to continue?", "Download All Headers", MessageBoxButtons.YesNo, MessageBoxIcon.Question ) != DialogResult.Yes ) { return; } } foreach( IResource group in resources.ValidResources ) { NntpClientHelper.DownloadAllHeadersFromGroup( group ); } } } /** * mark all as read */ public class MarkAllAsReadAction: ActionOnResource { public override void Execute( IActionContext context ) { IResourceList resources = context.SelectedResources; foreach( string resType in resources.GetAllTypes() ) { if( resType == NntpPlugin._newsServer ) { if( MessageBox.Show( Core.MainWindow, "This action will mark all messages in all newsgroups on the server as read. Do you wish to continue?", "Mark All as Read", MessageBoxButtons.YesNo, MessageBoxIcon.Question ) != DialogResult.Yes ) { return; } break; } } MarkGroupsRead( resources ); } public override void Update( IActionContext context, ref ActionPresentation presentation ) { base.Update( context, ref presentation ); if( presentation.Visible ) { IResourceList selected = context.SelectedResources; if( selected.Count == 1 ) { IResource resource = selected[ 0 ]; if( resource.Type == NntpPlugin._newsFolder && NewsFolders.IsDefaultFolder( resource ) && !NewsFolders.IsSentItems( resource ) ) { presentation.Visible = false; } else if( resource.Type == NntpPlugin._newsServer ) { presentation.Enabled = new ServerResource( resource ).SubscribedGroupsCount > 0; } } } } internal static void MarkGroupsRead( IResourceList groups ) { foreach( IResource group in groups ) { if( group.Type != NntpPlugin._newsGroup && !NewsFolders.IsDefaultFolder( group ) ) { MarkGroupsRead( new NewsTreeNode( group ).Children ); } else { Core.ResourceAP.QueueJob( JobPriority.Immediate, "Mark newsgroup as read", new ResourceDelegate( MarkGroupRead ), group ); } } } internal static void MarkGroupRead( IResource group ) { IResourceList articles = group.GetLinksTo( null, NntpPlugin._propTo ); articles = articles.Intersect( Core.ResourceStore.FindResourcesWithProp( null, NntpPlugin._propIsUnread ), true ); foreach( IResource article in articles ) { article.SetProp( NntpPlugin._propIsUnread, false ); } } } /** * subscribe action */ public class SubscribeAction: ActionOnResource { private IResourceList _groups; public override void Execute( IActionContext context ) { if( _groups != null ) { Core.ResourceAP.QueueJob( JobPriority.Immediate, new MethodInvoker( SubscribeToGroups ) ); } } private void SubscribeToGroups() { foreach( IResource group in _groups ) { NewsgroupResource groupResource = new NewsgroupResource( group ); if( !groupResource.IsSubscribed ) { NntpPlugin.Subscribe2Group( groupResource.Name, groupResource.Server ); } } } public override void Update( IActionContext context, ref ActionPresentation presentation ) { base.Update( context, ref presentation ); if( presentation.Visible ) { bool visible = false; IResourceList groups = Core.ResourceStore.EmptyResourceList; IResourceList resources = context.SelectedResources; foreach( IResource res in resources ) { if( res.Type == NntpPlugin._newsGroup ) { groups = groups.Union( res.ToResourceList() ); } else if ( res.Type == NntpPlugin._newsFolder || res.Type == NntpPlugin._newsServer ) { groups = groups.Union( new NewsTreeNode( res ).Groups ); } } foreach( IResource group in groups ) { if( !new NewsgroupResource( group ).IsSubscribed ) { _groups = groups; visible = true; break; } } presentation.Visible = visible; } } } /** * unsubscribe action */ public class UnsubscribeAction: ActionOnResource { public override void Execute( IActionContext context ) { bool deleteArticles; IResourceList resources = context.SelectedResources; if( UnsubscribeForm.Unsubscribe( resources, out deleteArticles ) == DialogResult.OK ) { if( resources.Count == 1 ) { Core.ResourceAP.QueueJob( JobPriority.Immediate, new UnsubscribeDelegate( Unsubscribe ), resources[ 0 ], deleteArticles ); } else { foreach( IResource group in resources ) { Core.ResourceAP.QueueJob( JobPriority.Immediate, new UnsubscribeDelegate( Unsubscribe ), group, deleteArticles ); } } } } public override void Update( IActionContext context, ref ActionPresentation presentation ) { base.Update( context, ref presentation ); presentation.Text = ( NewsgroupResource.AllUnsubscribed( context.SelectedResources ) ) ? "Delete" : "Unsubscribe"; } internal delegate void UnsubscribeDelegate( IResource group, bool deleteArticles ); internal static void Unsubscribe( IResource group, bool deleteArticles ) { NewsgroupResource groupBO = new NewsgroupResource( group ); string groupName = groupBO.Name; IResource server = groupBO.Server; if( deleteArticles ) { IResourceList articles = group.GetLinksTo( NntpPlugin._newsArticle, NntpPlugin._propTo ); group.Delete(); List contactIDs = new List(); foreach( IResource article in articles ) { // do not delete crossposted articles if( article.GetLinksFrom( NntpPlugin._newsGroup, NntpPlugin._propTo ).Count == 0 ) { IResource from = article.GetLinkProp( Core.ContactManager.Props.LinkFrom ); if( from != null && contactIDs.IndexOf( from.Id ) < 0 ) { contactIDs.Add( from.Id ); } article.GetLinksOfType( null, NntpPlugin._propAttachment ).DeleteAll(); article.Delete(); } } Core.ContactManager.DeleteUnusedContacts( Core.ResourceStore.ListFromIds( contactIDs, false ) ); } if( server != null ) { ServerResource serverResource = new ServerResource( server ); serverResource.UnsubscribeFromGroup( groupName ); if( !deleteArticles ) { groupBO.InvalidateDisplayName( serverResource.AbbreviateLevel ); } } NntpPlugin.CheckGroups(); } } /** * action for swithing threaded mode */ public class SwitchNewsThreadedModeAction: SwitchThreadedModeAction { public SwitchNewsThreadedModeAction() : base( NntpPlugin._newsGroup, NntpPlugin._newsFolder, NntpPlugin._newsServer ) {} } /** * action for swithing groups' and servers' "Hide Read Messages" mode */ public class SwitchGroupsNServersUnreadModeAction: SwitchUnreadModeAction { public SwitchGroupsNServersUnreadModeAction() : base( NntpPlugin._newsGroup, NntpPlugin._newsServer ) {} } /** * action for swithing folders' "Hide Read Messages" mode */ public class SwitchFoldersUnreadModeAction: SwitchUnreadModeAction { public SwitchFoldersUnreadModeAction() : base( NntpPlugin._newsFolder ) {} public override void Update( IActionContext context, ref ActionPresentation presentation ) { base.Update( context, ref presentation ); IResourceList resources = context.SelectedResources; if( presentation.Visible ) { foreach( IResource res in resources ) { if( NewsFolders.IsDefaultFolder( res ) ) { presentation.Visible = false; break; } } } } } /** * Shows the selected news article in context (in the Newsgroups pane). */ public class GoToConversationAction: ActionOnSingleResource { public override void Execute( IActionContext context ) { if ( Core.WorkspaceManager.ActiveWorkspace != null ) { bool anyInCurrentWorkspace = false; IResourceList newsgroups = context.SelectedResources [0].GetLinksFrom( NntpPlugin._newsGroup, NntpPlugin._propTo ); foreach( IResource newsgroup in newsgroups ) { if ( newsgroup.HasLink( "WorkspaceVisible", Core.WorkspaceManager.ActiveWorkspace ) ) { anyInCurrentWorkspace = true; break; } } if ( !anyInCurrentWorkspace ) { Core.UIManager.BeginUpdateSidebar(); Core.WorkspaceManager.ActiveWorkspace = null; Core.UIManager.EndUpdateSidebar(); } } Core.UIManager.DisplayResourceInContext( context.SelectedResources [0], true ); Core.ResourceBrowser.FocusResourceList(); } public override void Update( IActionContext context, ref ActionPresentation presentation ) { base.Update( context, ref presentation ); if ( presentation.Visible && Core.ResourceBrowser.OwnerResource != null ) { // hide "Go to Conversation" when the newsgroup containing the article // is displayed, because it won't have any effect in that case IResourceList newsgroups = context.SelectedResources [0].GetLinksFrom( NntpPlugin._newsGroup, NntpPlugin._propTo ); if ( newsgroups.IndexOf( Core.ResourceBrowser.OwnerResource ) >= 0 ) { presentation.Visible = false; } } } } /** * base class for the Reply2Sender and the ForwardArticle actions */ public abstract class ReplyForwardAction : IAction { internal EditMessageForm _form; protected void CheckForm() { if( _form != null && Settings.CloseOnReply ) { _form.Close(); } } protected static IEmailService GetEmailService() { return (IEmailService) Core.PluginLoader.GetPluginService( typeof( IEmailService ) ); } public virtual void Execute( IActionContext context ) { if( _form == null && context.Kind == ActionContextKind.Keyboard ) { _form = context.Instance as EditMessageForm; } IResource article = context.SelectedResources[ 0 ]; if( Settings.MarkAsReadOnReplyAndFormard && article.HasProp( NntpPlugin._propIsUnread ) ) { new ResourceProxy( article ).SetProp( NntpPlugin._propIsUnread, false ); } } public virtual void Update( IActionContext context, ref ActionPresentation presentation ) { presentation.Visible = presentation.Enabled = true; // Show and enable by default // If on the message editing form, never hide this button so that it won't pop out suddenly when we save the message and the resource appears // In other cases, preserve the prekv behavior where the action disapperas if unavailable bool isAvail = ((context.SelectedResources != null) && (context.SelectedResources.Count != 0)); if(_form != null) presentation.Enabled = isAvail; else presentation.Visible = isAvail; // Additional restrictions presentation.Enabled = presentation.Enabled && (GetEmailService() != null) && (context.SelectedResources.Count == 1) && context.SelectedResources[ 0 ].HasProp( Core.Props.LongBody ); } } /** * Replies to sender of an article */ public class Reply2Sender: ReplyForwardAction { public Reply2Sender() {} internal Reply2Sender( EditMessageForm form ) { _form = form; } public override void Execute( IActionContext context ) { base.Execute( context ); IEmailService service = GetEmailService(); IResource article = context.SelectedResources[ 0 ]; string subject = article.GetPropText( Core.Props.Subject ); if( !subject.ToLower().StartsWith( "re:" ) ) { subject = "Re: " + subject; } string quote = Core.MessageFormatter.QuoteMessage( article, Core.Props.LongBody ); CheckForm(); IContactManager cm = Core.ContactManager; IResource from = article.GetLinkProp( cm.Props.LinkFrom ); string name = cm.GetFullName( from ); IResource emailAcc = article.GetLinkProp( cm.Props.LinkEmailAcctFrom ); string email = ( emailAcc == null ) ? string.Empty : emailAcc.GetPropText( cm.Props.EmailAddress ); service.CreateEmail( subject, quote, EmailBodyFormat.PlainText, new[] { new EmailRecipient( name, email ) }, null, false ); } public override void Update(IActionContext context, ref ActionPresentation presentation) { if(context.SelectedResources.Count == 1) { // There's exactly one resource selected; check its type // Ensure it's not a local article as we cannot reply to an author of a local article presentation.Enabled = presentation.Visible = (context.SelectedResources[0].Type == NntpPlugin._newsArticle); } else presentation.Visible = false; // Can be applied when there is exactly one resource } } /** * Forwards an article */ public class ForwardArticle: ReplyForwardAction { public ForwardArticle() {} internal ForwardArticle( EditMessageForm form ) { _form = form; } public override void Execute( IActionContext context ) { base.Execute( context ); IEmailService service = GetEmailService(); StringBuilder forwardedBodyBuilder = StringBuilderPool.Alloc(); try { IResource article = context.SelectedResources[ 0 ]; string subject = article.GetPropText( Core.Props.Subject ); forwardedBodyBuilder.AppendFormat( "\r\n\r\nFrom: {0}", NewsContactHelper.RestoreFromField( article ) ); forwardedBodyBuilder.AppendFormat( "\r\nSent: {0}\r\nNewsgroups: ", article.GetDateProp( Core.Props.Date ) ); IResourceList groups = article.GetLinksFrom( NntpPlugin._newsGroup, NntpPlugin._propTo ); if( groups.Count > 0 ) { forwardedBodyBuilder.Append( groups[ 0 ].GetPropText( Core.Props.Name ) ); for( int i = 1; i < groups.Count; ++i ) { forwardedBodyBuilder.AppendFormat( ", {0}", groups[ i ].GetPropText( Core.Props.Name ) ); } } forwardedBodyBuilder.AppendFormat( "\r\nSubject: {0}\r\n\r\n{1}", subject, article.GetPropText( Core.Props.LongBody ) ); CheckForm(); service.CreateEmail( "Fw: " + subject, forwardedBodyBuilder.ToString(), EmailBodyFormat.PlainText, (EmailRecipient[]) null, null, true ); } finally { StringBuilderPool.Dispose( forwardedBodyBuilder ); } } } /** * open article in separate window */ public class OpenArticleInSeparateWindow : ActionOnResource { public override void Execute( IActionContext context ) { foreach( IResource selected in context.SelectedResources.ValidResources ) { if( NewsFolders.IsInFolder( selected, NewsFolders.Drafts ) || NewsFolders.IsInFolder( selected, NewsFolders.Outbox ) ) { EditMessageForm.EditAndPostMessage( selected ); } else { EditMessageForm.OpenMessageInSeparateWindow( selected ); } } } public override void Update( IActionContext context, ref ActionPresentation presentation ) { base.Update( context, ref presentation ); if( presentation.Visible ) { foreach( IResource res in context.SelectedResources.ValidResources ) { if( res.HasProp( Core.Props.IsDeleted ) ) { presentation.Visible = false; return; } } } } } /** * toggle formatting for article's preview */ public class ToggleFormattingAction : ActionOnSingleResource { public override void Execute( IActionContext context ) { IResource resource = context.SelectedResources[ 0 ]; ResourceProxy proxy = new ResourceProxy( resource ); if( resource.HasProp( "NoFormat" ) ) { proxy.DeleteProp( "NoFormat" ); } else { proxy.SetProp( "NoFormat", true ); } Core.ResourceBrowser.RedisplaySelectedResource(); } public override void Update( IActionContext context, ref ActionPresentation presentation ) { base.Update( context, ref presentation ); if( presentation.Visible ) { presentation.Text = ( context.SelectedResources[ 0 ].HasProp( "NoFormat" ) ) ? "Show as Formatted Text" : "Show as Plain Text"; } } } /** * action for viewing headers */ public class ViewHeadersAction : ActionOnSingleResource { public override void Execute( IActionContext context ) { HeadersViewer.ViewHeaders( context.SelectedResources[ 0 ] ); } } /** * action for folder creation */ public class NewFolderAction : ActionOnSingleResource { public override void Execute( IActionContext context ) { IResource folder = NewsFolders.GetNewsFolderResource( context.SelectedResources[ 0 ], "New Folder", true ); NntpPlugin._newsgroupsTreePane.EditResourceLabel( folder ); } public override void Update( IActionContext context, ref ActionPresentation presentation ) { base.Update( context, ref presentation ); if( presentation.Visible ) { IResource resource = context.SelectedResources[ 0 ]; presentation.Visible = resource.Type == NntpPlugin._newsServer || ( resource.Type == NntpPlugin._newsFolder && !NewsFolders.IsDefaultFolder( resource ) ); } } } /** * action for deleting newsfolder */ public class DeleteFolderAction : ActionOnSingleResource { public override void Execute( IActionContext context ) { IResource folder = context.SelectedResources[ 0 ]; bool allowDelete = false; if( NewsFolders.IsDefaultFolder( folder ) ) { if( folder.GetLinksTo( null, NntpPlugin._propTo ).Count == 0 || AskDeleteFolder() == DialogResult.Yes ) { allowDelete = true; } } else { if( new NewsTreeNode( folder ).Children.Count == 0 || AskDeleteFolder() == DialogResult.Yes ) { allowDelete = true; } } if( allowDelete ) { Core.ResourceAP.QueueJob( JobPriority.Immediate, new ResourceDelegate( DeleteFolderImpl ), folder ); } } private static DialogResult AskDeleteFolder() { return MessageBox.Show( Core.MainWindow, "Do you want to delete the folder and its contents?", "Delete News Folder", MessageBoxButtons.YesNo, MessageBoxIcon.Question ); } public override void Update( IActionContext context, ref ActionPresentation presentation ) { base.Update( context, ref presentation ); if( presentation.Visible ) { presentation.Visible = !NewsFolders.IsDefaultFolder( context.SelectedResources[ 0 ] ) || Core.ResourceStore.GetAllResources( NntpPlugin._newsServer ).Count == 0; } } private static void DeleteFolderImpl( IResource folder ) { NewsTreeNode node = new NewsTreeNode( folder ); IResourceList childs = node.Children; foreach( IResource child in childs ) { if( child.Type == NntpPlugin._newsGroup ) { UnsubscribeAction.Unsubscribe( child, true ); } else { DeleteFolderImpl( child ); } } if( NewsFolders.IsDefaultFolder( folder ) ) { folder.GetLinksTo( null, NntpPlugin._propTo ).DeleteAll(); } folder.Delete(); } } /** * action for refreshing article */ public class RefreshArticleAction: ActionOnResource { public override void Execute( IActionContext context ) { RefreshArticleImpl( context.SelectedResourcesExpanded ); } public override void Update( IActionContext context, ref ActionPresentation presentation ) { base.Update(context, ref presentation ); if( presentation.Visible ) { foreach( IResource res in context.SelectedResourcesExpanded.ValidResources ) { if( res.HasProp( Core.Props.IsDeleted ) ) { presentation.Visible = false; return; } } IResource article = context.SelectedResourcesExpanded[ 0 ]; presentation.Text = ( article.HasProp( NntpPlugin._propHasNoBody ) ) ? "Download" : "Refresh"; UpdateActionIfNetworkUnavailable.Update( ref presentation ); } } internal static void RefreshArticleImpl( IResourceList articles ) { if( !Core.ResourceAP.IsOwnerThread ) { Core.ResourceAP.QueueJob( JobPriority.Immediate, new ResourceListDelegate( RefreshArticleImpl ), articles ); } else { foreach( IResource article in articles ) { article.SetProp( NntpPlugin._propHasNoBody, true ); article.GetLinksOfType( null, NntpPlugin._propAttachment ).DeleteAll(); ArticlePreviewPane pane = NntpPlugin._previewPane; if( pane != null && pane.IsArticleDisplayed( article ) ) { pane.RedisplayArticle( article ); } else { IResourceList groups = article.GetLinksOfType( NntpPlugin._newsGroup, NntpPlugin._propTo ); foreach( IResource groupRes in groups.ValidResources ) { IResource server = new NewsgroupResource( groupRes ).Server; if( server != null ) { NntpConnection articlesConnection = NntpConnectionPool.GetConnection( server, "foreground" ); NntpDownloadArticleUnit downloadUnit = new NntpDownloadArticleUnit( article, groupRes, JobPriority.Immediate, true ); articlesConnection.StartUnit( Int32.MaxValue - 1, downloadUnit ); } } } } } } } /// /// Action for copying article URL. /// public class CopyArticleURLAction: ActionOnSingleResource { public override void Execute( IActionContext context ) { try { string sUri = GetArticleUri(context.SelectedResources[0]); if(sUri != null) Clipboard.SetDataObject(sUri); else MessageBox.Show(Core.MainWindow, "Could not get the article URL.", Core.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Error); } catch( ExternalException e ) { Utils.DisplayException( Core.MainWindow, e, "Error" ); } } /// /// Gets an uri for the news article. May return Null in case of problems. /// /// /// public static string GetArticleUri(IResource article) { string articleId = ParseTools.UnescapeCaseSensitiveString( article.GetPropText( NntpPlugin._propArticleId ) ); if( articleId.Length > 0 ) { IResourceList servers = Core.ResourceStore.EmptyResourceList; IResourceList groups = article.GetLinksFrom( NntpPlugin._newsGroup, NntpPlugin._propTo ); foreach( IResource group in groups ) { IResource server = new NewsgroupResource( group ).Server; if( server != null ) { servers = servers.Union( server.ToResourceList(), true ); } } if( servers.Count > 0 ) { servers.Sort( "LastUpdated", false ); return "news://" + new ServerResource(servers[0]).Name + "/" + articleId.Trim('<', '>'); } } return null; } } /** * action for cancelling article */ public class CancelArticleAction: ActionOnSingleResource { private IResource _controlArticle; public override void Execute( IActionContext context ) { if( Settings.ConfirmCancel ) { MessageBoxWithCheckBox.Result result = MessageBoxWithCheckBox.ShowYesNo( Core.MainWindow, "Do you wish to cancel selected article?", "Confirm Cancellation", "Never &ask confirmation", false ); if( result.Checked ) { Settings.ConfirmCancel.Save( false ); } if( result.IdPressedButton != (int)DialogResult.Yes ) { return; } } IResource article = context.SelectedResources[ 0 ]; string id = article.GetPropText( NntpPlugin._propArticleId ); if( id.Length > 0 ) { string subj = "cancel " + ParseTools.UnescapeCaseSensitiveString( id ); string fromField = NewsContactHelper.RestoreFromField( article ); string nntpText = "From: " + fromField + "\r\nSubject: " + subj + "\r\nControl: " + subj + "\r\nNewsgroups: "; IResourceList groups = article.GetLinksFrom( NntpPlugin._newsGroup, NntpPlugin._propTo ); if ( groups.Count > 0 ) { nntpText += new NewsgroupResource( groups[ 0 ] ).Name; for( int i = 1; i < groups.Count; ++i ) { nntpText += ','; nntpText += new NewsgroupResource( groups[ i ] ).Name; } } nntpText += "\r\n\r\n.\r\n"; _controlArticle = NewsFolders.PlaceArticle( null, NewsFolders.Drafts, groups, fromField, string.Empty, string.Empty, string.Empty, string.Empty, nntpText, null ); NntpClientHelper.PostArticle( _controlArticle, ControlPostFinished, true ); } } public override void Update( IActionContext context, ref ActionPresentation presentation ) { base.Update( context, ref presentation ); if( presentation.Visible && Core.SettingStore.ReadBool( "NNTP", "CancelDenied", true ) ) { IContactManager contactManager = Core.ContactManager; IResource from = context.SelectedResources[ 0 ].GetLinkProp( contactManager.Props.LinkFrom ); if( from == null || !contactManager.GetContact( from ).IsMyself ) { presentation.Enabled = false; } UpdateActionIfNetworkUnavailable.Update( ref presentation ); } } private void ControlPostFinished( AsciiProtocolUnit unit ) { string error = ( (NntpPostArticleUnit) unit ).Error; if( error != null ) { Core.UserInterfaceAP.QueueJob( new LineDelegate( DisplayError ), error ); } new ResourceProxy( _controlArticle ).DeleteAsync(); } private static void DisplayError( string error ) { MessageBox.Show( "Cancellation of the article failed.\r\nError details: " + error, "Cancellation Failed", MessageBoxButtons.OK, MessageBoxIcon.Error ); } } /** * action for deleting single news server */ public class DeleteNewsServerAction: ActionOnSingleResource { public override void Execute( IActionContext context ) { WarnAndDelete( context.SelectedResources[ 0 ], Core.MainWindow ); } public override void Update(IActionContext context, ref ActionPresentation presentation) { base.Update( context, ref presentation ); if( presentation.Visible ) { presentation.Text = "Remove"; } } internal static void WarnAndDelete( IResource server, IWin32Window parentWindow ) { IResourceList groups = new NewsTreeNode( server ).Groups; if( groups.Count == 0 || MessageBox.Show( parentWindow, "Are you sure you want to remove the '" + server.DisplayName + "' news server with all its groups which you are subscribed to?", "Warning", MessageBoxButtons.YesNo, MessageBoxIcon.Question ) == DialogResult.Yes ) { Cursor old = Cursor.Current; Cursor.Current = Cursors.WaitCursor; try { Core.ResourceAP.RunUniqueJob( new ResourceDelegate( DoDelete ), server ); } finally { Cursor.Current = old; } } } private static void DoDelete( IResource resource ) { IResourceList children = new NewsTreeNode( resource ).Children; IAsyncProcessor resourceAP = Core.ResourceAP; foreach( IResource child in children ) { if( child.Type == NntpPlugin._newsGroup ) { resourceAP.QueueJob( JobPriority.Immediate, new UnsubscribeAction.UnsubscribeDelegate( UnsubscribeAction.Unsubscribe ), child, true ); } else { DoDelete( child ); } } resourceAP.QueueJob( JobPriority.Immediate, new MethodInvoker( resource.Delete ) ); } } /** * action for searching in a newsgroup */ public class SearchInNewsgroup: SimpleAction { public override void Execute( IActionContext context ) { IResource template = Core.ResourceStore.FindUniqueResource( FilterManagerProps.ConditionTemplateResName, Core.Props.Name, "Appeared in the %specified% newsgroup(s)" ); IResource condition = FilterConvertors.InstantiateTemplate( template, context.SelectedResources, new[]{ "Article" } ); Core.FilteringFormsManager.ShowAdvancedSearchForm( "", new[]{ "Article" }, new[] { condition }, null ); } public override void Update( IActionContext context, ref ActionPresentation presentation ) { presentation.Enabled = presentation.Visible = context.SelectedResources.Count > 0; if( context.SelectedResources.Count > 1 ) presentation.Text = "Search in these Newsgroups"; } } /** * action for searching in a newsgroup */ public class SearchInNewsgroupFolder: ActionOnResource { public override void Execute( IActionContext context ) { IResourceList selection = Core.ResourceStore.EmptyResourceList; foreach( IResource folder in context.SelectedResources ) { selection = selection.Union( folder.GetLinksTo( "NewsGroup", "Parent" )); } IResource template = Core.ResourceStore.FindUniqueResource( FilterManagerProps.ConditionTemplateResName, Core.Props.Name, "Appeared in the %specified% newsgroup(s)" ); IResource condition = FilterConvertors.InstantiateTemplate( template, selection, new[]{ "Article" } ); Core.FilteringFormsManager.ShowAdvancedSearchForm( "", new[]{ "Article" }, new[] { condition }, null ); } public override void Update( IActionContext context, ref ActionPresentation presentation ) { base.Update( context, ref presentation ); if ( presentation.Visible && presentation.Enabled ) { int count = 0; foreach( IResource res in context.SelectedResources.ValidResources ) { count += res.GetLinksTo( "NewsGroup", Core.Props.Parent ).Count; if ( count > 0 ) { break; } } if ( count == 0 ) { if ( context.Kind == ActionContextKind.ContextMenu ) { presentation.Visible = false; } else { presentation.Enabled = false; } } } } } /** * action for saving articles as files */ public class SaveAsAction : ActionOnResource { public override void Execute( IActionContext context ) { IResourceList articles = context.SelectedResources; ISettingStore settings = Core.SettingStore; SaveFileDialog dlg = new SaveFileDialog(); dlg.RestoreDirectory = true; dlg.InitialDirectory = settings.ReadString( "NNTP", "SaveAsDialog.InitialDirectory", Environment.GetFolderPath( Environment.SpecialFolder.Personal ) ); dlg.Filter = "txt files (*.txt)|*.txt|Outlook Express files (*.nws)|*.nws|All files (*.*)|*.*"; dlg.FilterIndex = settings.ReadInt( "NNTP", "SaveAsDialog.FilterIndex", 0 ); if( articles.Count == 1 ) { string name = articles[ 0 ].DisplayName; for( int i = 0; i < name.Length; ++i ) { char c = name[ i ]; if( !Char.IsWhiteSpace( c ) && !Char.IsLetterOrDigit( c ) ) { name = name.Replace( c, '_' ); } } dlg.FileName = name; } if( dlg.ShowDialog() == DialogResult.OK ) { try { settings.WriteString( "NNTP", "SaveAsDialog.InitialDirectory", IOTools.GetDirectoryName( new FileInfo( dlg.FileName ) ) ); settings.WriteInt( "NNTP", "SaveAsDialog.FilterIndex", dlg.FilterIndex ); IResourceList groups = Core.ResourceStore.EmptyResourceList; for( int i = 0; i < articles.Count && groups.Count == 0; ++i ) { groups = articles[ i ].GetLinksFrom( NntpPlugin._newsGroup, NntpPlugin._propTo ); } Encoding encoding = ( groups.Count == 0 ) ? Encoding.Default : Encoding.GetEncoding( new ServerResource( new NewsgroupResource( groups[ 0 ] ).Server ).Charset ); Stream stream = dlg.OpenFile(); if( stream != null ) { using( StreamWriter writer = new StreamWriter( stream, encoding ) ) { foreach( IResource article in articles ) { if( article.HasProp( NntpPlugin._propArticleHeaders ) ) { writer.WriteLine( article.GetPropText( NntpPlugin._propArticleHeaders ) ); } if( article.HasProp( Core.Props.LongBody ) ) { writer.WriteLine( article.GetPropText( Core.Props.LongBody ) ); } else if( article.HasProp( NntpPlugin._propHtmlContent ) ) { writer.WriteLine( article.GetPropText( NntpPlugin._propHtmlContent ) ); } if( article != articles[ articles.Count - 1 ] ) { writer.WriteLine(); writer.WriteLine( '.' ); writer.WriteLine(); } } } } } catch {} } } } /** * action for switching the "mark all as read on exit" mode */ public class SwitchMarkReadAction : ActionOnSingleResource { private readonly string _propName; public SwitchMarkReadAction( string propName ) { _propName = propName; } public override void Execute( IActionContext context ) { IResource resource = context.SelectedResources[ 0 ]; ResourceProxy proxy = new ResourceProxy( resource ); if( resource.HasProp( _propName ) ) { proxy.DeleteProp( _propName ); } else { proxy.SetProp( _propName, true ); } } public override void Update( IActionContext context, ref ActionPresentation presentation ) { base.Update( context, ref presentation ); if( presentation.Visible ) { IResource resource = context.SelectedResources[ 0 ]; if( presentation.Visible == !NewsFolders.IsDefaultFolder( resource ) ) { presentation.Checked = resource.HasProp( _propName ); } } } } public class StopThreadAction : ActionOnSingleResource { private const string _cNntpIniSection = "NNTP"; private const string _cShowConfirmKey = "ShowMarkReadOnStopThread"; private const string _cMarkReadOnStopKey = "MarkAsReadOnThreadStop"; private const string _cMessage = "Mark all articles in thread read?"; private const string _cTitle = "Mark Thread Read"; private const string _cQuestion = "Do not show this message again"; private delegate void MarkReadDelegate( IResource res, bool read ); public override void Execute( IActionContext context ) { IResource selected = context.SelectedResources[ 0 ]; IResource root; bool hasProp = ConversationBuilder.CheckPropOnParents( selected, NntpPlugin._propIsIgnoredThread, out root ); if( !hasProp ) { bool markAsReadOnStop = Core.SettingStore.ReadBool( _cNntpIniSection, _cMarkReadOnStopKey, true ); bool showConfirmMarkAsRead = Core.SettingStore.ReadBool( _cNntpIniSection, _cShowConfirmKey, true ); bool isCtrlKey = (Control.ModifierKeys & Keys.Control ) > 0; if( showConfirmMarkAsRead || isCtrlKey ) { MessageBoxWithCheckBox.Result result = MessageBoxWithCheckBox.ShowYesNo( Core.MainWindow, _cMessage, _cTitle, _cQuestion, !showConfirmMarkAsRead ); if( result.Checked ) Core.SettingStore.WriteBool( _cNntpIniSection, _cShowConfirmKey, false ); markAsReadOnStop = (result.IdPressedButton == (int)DialogResult.Yes); } if( markAsReadOnStop ) { Core.ResourceAP.RunJob( new MarkReadDelegate( ConversationBuilder.MarkConversationRead ), selected, true ); } ResourceProxy proxy = new ResourceProxy( selected ); proxy.BeginUpdate(); proxy.SetProp( NntpPlugin._propIsIgnoredThread, true ); proxy.SetProp( NntpPlugin._propThreadVisibilityToggleDate, DateTime.Now ); proxy.EndUpdate(); } else { DateTime ignoreStartDate = root.GetDateProp( NntpPlugin._propThreadVisibilityToggleDate ); IResourceDeleter deleter = Core.PluginLoader.GetResourceDeleter( NntpPlugin._newsArticle ); // Collect the thread, take only deleted resources. // If a deleted resource was received after the thread was // marked as "Stop updating" then we can Undelete it. IResourceList thread = ConversationBuilder.UnrollConversation( root ); foreach( IResource article in thread ) { DateTime dateTime = article.GetDateProp( Core.Props.Date ); if( article.HasProp( Core.Props.IsDeleted ) && dateTime > ignoreStartDate ) { deleter.UndeleteResource( article ); } } new ResourceProxy( root ).DeleteProp( NntpPlugin._propIsIgnoredThread ); } } public override void Update( IActionContext context, ref ActionPresentation presentation ) { base.Update( context, ref presentation ); if( presentation.Visible ) { if( ConversationBuilder.CheckPropOnParents( context.SelectedResources[ 0 ], NntpPlugin._propIsIgnoredThread )) { presentation.Text = "Restore Ignored Articles"; } } } } internal class UpdateActionIfNetworkUnavailable { public static void Update( ref ActionPresentation presentation ) { if( presentation.Visible && !Utils.IsNetworkConnected() ) { presentation.ToolTip = NntpPlugin._networkUnavailable; presentation.Enabled = false; } } } }