Implement asynchronous processing during sending OUT-538
authorThomas
Fri, 01 Feb 2019 09:57:03 +0100
branchOUT-538
changeset 2561 26c3a4099ae2
parent 2560 9af9e042ea9f
child 2562 3dbba3e9ed86
Implement asynchronous processing during sending
Extensions/MailItemExtensions.cs
ThisAddIn.cs
Wrappers/WatchedWindow.cs
--- a/Extensions/MailItemExtensions.cs	Wed Jan 30 15:20:01 2019 +0100
+++ b/Extensions/MailItemExtensions.cs	Fri Feb 01 09:57:03 2019 +0100
@@ -6,6 +6,8 @@
 using System.IO;
 using System.Linq;
 using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Forms;
 using Outlook = Microsoft.Office.Interop.Outlook;
 
 namespace pEp
@@ -19,6 +21,7 @@
         public const string                     USER_PROPERTY_KEY_INSPECTOR_CLOSED      = "inspectorClosed";
         public const string                     USER_PROPERTY_KEY_IS_INCOMING           = "isIncoming";
         public const string                     USER_PROPERTY_KEY_IS_MIRROR             = "isMirror";
+        public const string                     USER_PROPERTY_KEY_PROCESSING_STATE      = "processingState";
         public const string                     USER_PROPERTY_KEY_REPLY_ACTION          = "replyAction";
         public const string                     UNKNOWN_SENDER                          = "unknown";
 
@@ -42,6 +45,16 @@
             KeyImport
         }
 
+        /// <summary>
+        /// Enumeration defining the processing state of a mail item during sending.
+        /// </summary>
+        public enum ProcessingState
+        {
+            Undefined,
+            ProcessInBackground,
+            Processed
+        }
+
         // Default values for pEp properties.
         private static readonly object      AUTO_CONSUME_DEFAULT            = null;
         private static readonly bool        FORCE_UNENCRYPTED_DEFAULT       = false;
@@ -53,6 +66,7 @@
         private static readonly object      FORCE_PROTECTION_DEFAULT        = null;
         private static readonly object      KEY_IMPORT_DEFAULT              = null;
 
+
         /**************************************************************
          * 
          * Extensions to Access a PEPProperty
@@ -252,6 +266,69 @@
         }
 
         /// <summary>
+        /// Adds a reply or forward icon to the original mail item that this item is a reply
+        /// or forwarded message to.
+        /// </summary>
+        /// <param name="omi">The Outlook mail item to process with.</param>
+        /// <param name="originalEntryId">The EntryId of the original mail item.</param>
+        public static void AddReplyIconsToOriginal(this Outlook.MailItem omi, 
+                                                   string originalEntryId)
+        {
+
+            Outlook.MailItem original = null;
+            try
+            {
+                bool iconSet = false;
+                string replyActionString = omi.GetUserProperty(MailItemExtensions.USER_PROPERTY_KEY_REPLY_ACTION) as string;
+                if (int.TryParse(replyActionString, out int replyAction))
+                {
+                    original = Globals.ThisAddIn.Application.Session.GetItemFromID(originalEntryId);
+
+                    switch ((FormRegionPreviewUnencrypted.ReplyAction)replyAction)
+                    {
+                        case FormRegionPreviewUnencrypted.ReplyAction.Reply:
+                        case FormRegionPreviewUnencrypted.ReplyAction.ReplyAll:
+                            {
+                                MapiHelper.SetProperty(original, MapiProperty.PidTagIconIndex, MapiPropertyValue.PidTagIconIndexFlags.Replied);
+                                iconSet = true;
+                            }
+                            break;
+                        case FormRegionPreviewUnencrypted.ReplyAction.Forward:
+                            {
+                                MapiHelper.SetProperty(original, MapiProperty.PidTagIconIndex, MapiPropertyValue.PidTagIconIndexFlags.Forwarded);
+                                iconSet = true;
+                            }
+                            break;
+                        default:
+                            {
+                                Log.Error("Application_ItemSend: Error getting reply type.");
+                            }
+                            break;
+                    }
+                }
+                else
+                {
+                    Log.Error("Application_ItemSend: Error parsing reply action string to int.");
+                }
+
+                if (iconSet)
+                {
+                    original.Save();
+                    omi.DeleteUserProperty(MailItemExtensions.USER_PROPERTY_KEY_ORIG_ENTRY_ID);
+                    omi.DeleteUserProperty(MailItemExtensions.USER_PROPERTY_KEY_REPLY_ACTION);
+                }
+            }
+            catch (Exception ex)
+            {
+                Log.Error("Application_ItemSend: Error setting icon. " + ex.ToString());
+            }
+            finally
+            {
+                original = null;
+            }
+        }
+
+        /// <summary>
         /// Deletes all pEp user properties and sets the UseTnef MAPI property
         /// to false to prevent Outlook adding the automatic winmail.dat attachment.
         /// </summary>
@@ -1920,6 +1997,126 @@
         }
 
         /// <summary>
+        /// Processes a mail item asynchronously and sends it out.
+        /// </summary>
+        /// <param name="omi">The Outlook mail item to process with.</param>
+        /// <param name="message">The message to process.</param>
+        public static async void ProcessAndSendMessageAsync(this Outlook.MailItem omi, PEPMessage message)
+        {
+            bool sendError = false;
+
+            PEPMessage processedMessage = await Task.Run(() =>
+            {
+                // Process outgoing message
+                MsgProcessor processor = new MsgProcessor();
+                if (processor.ProcessSentMessage(message, Globals.ThisAddIn.Settings.ExtraKeys, out PEPMessage msg) != Globals.ReturnStatus.Success)
+                {
+                    Log.Error("ProcessOutgoing: Error processing message.");
+                    sendError = true;
+                    return null;
+                }
+
+                return msg;
+            });
+
+            if (sendError == false)
+            {
+                try
+                {
+                    // Apply processed message to outgoing item
+                    processedMessage.ApplyTo(omi, false, false, false);
+
+                    // Set Reply or forward icon on original message if needed
+                    string originalEntryId = omi.GetUserProperty(MailItemExtensions.USER_PROPERTY_KEY_ORIG_ENTRY_ID) as string;
+                    if (string.IsNullOrEmpty(originalEntryId) == false)
+                    {
+                        omi.AddReplyIconsToOriginal(originalEntryId);
+                    }
+
+                    // Avoid creation of 'winmail.dat' attachment if needed
+                    if (processedMessage.IsSecure == false)
+                    {
+                        omi.AvoidWinmailDatAttachment();
+                    }
+
+                    omi.SetProcessingState(MailItemExtensions.ProcessingState.Processed);
+                    omi.Send();
+                }
+                catch (Exception ex)
+                {
+                    sendError = true;
+                    Log.Error("ProcessOutgoingAsync: Error sending message. " + ex.ToString());
+                }
+            }
+
+            if (sendError)
+            {
+                Log.Error("Application_ItemSend: Send failure.");
+                bool cancel = false;
+                omi.Display();
+
+                // Ask the user to continue (not for automatic messages)
+                if (omi?.GetIsAutoConsume() != true)
+                {
+                    string sendUnencryptedWarning = Properties.Resources.Message_SendError + Environment.NewLine + Environment.NewLine + Properties.Resources.Message_SendUnencryptedConfirmation;
+                    DialogResult result = MessageBox.Show(sendUnencryptedWarning,
+                                                          Properties.Resources.Message_TitlePEPError,
+                                                          MessageBoxButtons.YesNo,
+                                                          MessageBoxIcon.Error);
+                    cancel = (result != DialogResult.Yes);
+                }
+
+                Log.Info("Application_ItemSend: Error during sending. " + (cancel ? "User aborted sending." : "user chose to send mail unencrypted."));
+
+                if (cancel == false)
+                {
+                    omi.SetProcessingState(MailItemExtensions.ProcessingState.Processed);
+                    omi.Send();
+                }
+            }
+        }
+
+        /// <summary>
+        /// Gets the processing state (user property) of this message.
+        /// </summary>
+        /// <param name="omi">The Outlook mail item to process with.</param>
+        /// <returns>The processing state or null if no user property was found</returns>
+        public static MailItemExtensions.ProcessingState? GetProcessingState(this Outlook.MailItem omi)
+        {
+            if ((omi.GetUserProperty(MailItemExtensions.USER_PROPERTY_KEY_PROCESSING_STATE) is int processingState) &&
+                (Enum.IsDefined(typeof(MailItemExtensions.ProcessingState), processingState)))
+            {
+                return (MailItemExtensions.ProcessingState)processingState;
+            }
+
+            return null;
+        }
+
+        /// <summary>
+        /// Sets the processing state (user property) of this message.
+        /// </summary>
+        /// <param name="omi">The Outlook mail item to process with.</param>
+        /// <param name="processingState">The processing state to set.</param>
+        /// <returns>True if successfully set, otherwise false.</returns>
+        public static bool SetProcessingState(this Outlook.MailItem omi, MailItemExtensions.ProcessingState processingState)
+        {
+            bool success = false;
+
+            try
+            {
+                omi.SetUserProperty(MailItemExtensions.USER_PROPERTY_KEY_PROCESSING_STATE, (int)processingState, Outlook.OlUserPropertyType.olInteger);
+                success = true;
+            }
+            catch (Exception ex)
+            {
+                success = false;
+                Log.Error("SetProcessingState: Error occured. " + ex.ToString());
+            }
+
+            return success;
+        }
+
+        /// <summary>
         /// Gets the own pEp identity for the SendUsingAccount of the MailItem.
         /// Warning: This can return null.
         /// </summary>
--- a/ThisAddIn.cs	Wed Jan 30 15:20:01 2019 +0100
+++ b/ThisAddIn.cs	Fri Feb 01 09:57:03 2019 +0100
@@ -3327,8 +3327,17 @@
                 // Only process if pEp is enabled
                 if (omi?.GetIsSendProcessingEnabled() == true)
                 {
+                    // Check if a processing state has been set
+                    MailItemExtensions.ProcessingState? processingState = omi.GetProcessingState();
+
+                    // If the message has already been processed, just send it out
+                    if (processingState == MailItemExtensions.ProcessingState.Processed)
+                    {
+                        return;
+                    }
+
+                    // Check for special cases
                     bool processMessage = true;
-                    bool closedInspector = (omi.GetUserProperty(MailItemExtensions.USER_PROPERTY_KEY_INSPECTOR_CLOSED) != null);
 
                     // If S/MIME is enabled, add pEp header and do not process
                     if (omi.GetIsSMIMEEnabled())
@@ -3353,11 +3362,11 @@
                         }
                     }
 
+                    // If no special case was found, process normally
                     if (processMessage)
                     {
                         string sendUnencryptedWarning = Properties.Resources.Message_SendError + Environment.NewLine + Environment.NewLine + Properties.Resources.Message_SendUnencryptedConfirmation;
                         DialogResult result;
-                        MsgProcessor processor;
                         Globals.ReturnStatus status;
 
                         try
@@ -3367,75 +3376,39 @@
                             status = PEPMessage.Create(omi, out PEPMessage message);
                             if (status == Globals.ReturnStatus.Success)
                             {
-                                processor = new MsgProcessor();
-                                if (processor.ProcessSentMessage(message, Globals.ThisAddIn.Settings.ExtraKeys, out PEPMessage processedMessage) != Globals.ReturnStatus.Success)
+                                /* If the message was marked to be processed in the background, cancel the normal
+                                 * send process and process the message further in the dedicated method.
+                                 */ 
+                                if (processingState == MailItemExtensions.ProcessingState.ProcessInBackground)
                                 {
-                                    throw new Exception("Error processing message.");
+                                    cancel = true;
+                                    omi.ProcessAndSendMessageAsync(message);
+                                    return;
                                 }
-
-                                processedMessage.ApplyTo(omi, false, false, false);
-
-                                // Set Reply or forward icon on original message if needed
-                                string originalEntryId = omi.GetUserProperty(MailItemExtensions.USER_PROPERTY_KEY_ORIG_ENTRY_ID) as string;
-                                if (string.IsNullOrEmpty(originalEntryId) == false)
+                                else
                                 {
-                                    Outlook.MailItem original = null;
-                                    try
+                                    // Process outgoing message
+                                    MsgProcessor processor = new MsgProcessor();
+                                    if (processor.ProcessSentMessage(message, Globals.ThisAddIn.Settings.ExtraKeys, out PEPMessage processedMessage) != Globals.ReturnStatus.Success)
                                     {
-                                        bool iconSet = false;
-                                        string replyActionString = omi.GetUserProperty(MailItemExtensions.USER_PROPERTY_KEY_REPLY_ACTION) as string;
-                                        if (int.TryParse(replyActionString, out int replyAction))
-                                        {
-                                            original = Globals.ThisAddIn.Application.Session.GetItemFromID(originalEntryId);
-
-                                            switch ((FormRegionPreviewUnencrypted.ReplyAction)replyAction)
-                                            {
-                                                case FormRegionPreviewUnencrypted.ReplyAction.Reply:
-                                                case FormRegionPreviewUnencrypted.ReplyAction.ReplyAll:
-                                                    {
-                                                        MapiHelper.SetProperty(original, MapiProperty.PidTagIconIndex, MapiPropertyValue.PidTagIconIndexFlags.Replied);
-                                                        iconSet = true;
-                                                    }
-                                                    break;
-                                                case FormRegionPreviewUnencrypted.ReplyAction.Forward:
-                                                    {
-                                                        MapiHelper.SetProperty(original, MapiProperty.PidTagIconIndex, MapiPropertyValue.PidTagIconIndexFlags.Forwarded);
-                                                        iconSet = true;
-                                                    }
-                                                    break;
-                                                default:
-                                                    {
-                                                        Log.Error("Application_ItemSend: Error getting reply type.");
-                                                    }
-                                                    break;
-                                            }
-                                        }
-                                        else
-                                        {
-                                            Log.Error("Application_ItemSend: Error parsing reply action string to int.");
-                                        }
-
-                                        if (iconSet)
-                                        {
-                                            original.Save();
-                                            omi.DeleteUserProperty(MailItemExtensions.USER_PROPERTY_KEY_ORIG_ENTRY_ID);
-                                            omi.DeleteUserProperty(MailItemExtensions.USER_PROPERTY_KEY_REPLY_ACTION);
-                                        }
+                                        throw new Exception("Error processing message.");
                                     }
-                                    catch (Exception ex)
-                                    {
-                                        Log.Error("Application_ItemSend: Error setting icon. " + ex.ToString());
-                                    }
-                                    finally
+
+                                    // Apply processed message to outgoing item
+                                    processedMessage.ApplyTo(omi, false, false, false);
+
+                                    // Set Reply or forward icon on original message if needed
+                                    string originalEntryId = omi.GetUserProperty(MailItemExtensions.USER_PROPERTY_KEY_ORIG_ENTRY_ID) as string;
+                                    if (string.IsNullOrEmpty(originalEntryId) == false)
                                     {
-                                        original = null;
+                                        omi.AddReplyIconsToOriginal(originalEntryId);
                                     }
-                                }
-
-                                if (processedMessage.IsSecure == false)
-                                {
-                                    // Avoid creation of 'winmail.dat' attachment
-                                    omi.AvoidWinmailDatAttachment();
+
+                                    // Avoid creation of 'winmail.dat' attachment if needed
+                                    if (processedMessage.IsSecure == false)
+                                    {
+                                        omi.AvoidWinmailDatAttachment();
+                                    }
                                 }
                             }
                             else if (status == Globals.ReturnStatus.FailureNoConnection)
@@ -3461,34 +3434,22 @@
                         {
                             Log.Error("Application_ItemSend: Send failure, " + ex.ToString());
 
-                            if (closedInspector)
+                            if (processingState == MailItemExtensions.ProcessingState.ProcessInBackground)
                             {
                                 omi.Display();
                             }
 
                             // Ask the user to continue (not for automatic messages)
-                            bool continueSending = false;
                             if (omi?.GetIsAutoConsume() != true)
                             {
                                 result = MessageBox.Show(sendUnencryptedWarning,
                                                          Properties.Resources.Message_TitlePEPError,
                                                          MessageBoxButtons.YesNo,
                                                          MessageBoxIcon.Error);
-                                continueSending = (result == DialogResult.Yes);
+                                cancel = (result != DialogResult.Yes);
                             }
 
-                            if (continueSending)
-                            {
-                                // Send original message without processing
-                                cancel = false;
-                                Log.Info("Application_ItemSend: After send failure, user selected to send original unencrypted message.");
-                            }
-                            else
-                            {
-                                // Don't send the original message
-                                cancel = true;
-                                Log.Info("Application_ItemSend: Mail not sent. Message was automatic message or user cancelled sending.");
-                            }
+                            Log.Info("Application_ItemSend: Error during sending. " + (cancel ? "User aborted sending." : "user chose to send mail unencrypted."));
                         }
                     }
                 }
--- a/Wrappers/WatchedWindow.cs	Wed Jan 30 15:20:01 2019 +0100
+++ b/Wrappers/WatchedWindow.cs	Fri Feb 01 09:57:03 2019 +0100
@@ -1364,15 +1364,16 @@
                 if (cancel == false)
                 {
                     if ((this.Type == WindowType.Inspector) &&
-                        (this.cryptableMailItem.IsInlineResponse == false))
+                        (this.cryptableMailItem.IsInlineResponse == false) &&
+                        (this.CurrentMailItem.GetProcessingState() == null))
                     {                        
                         IntPtr hWnd = NativeMethods.GetActiveWindow();    
                         NativeMethods.SetWindowPos(hWnd, NativeMethods.HWND_BOTTOM, 0, 0, 0, 0, NativeMethods.SetWindowPosFlags.DoNotActivate |
                                                                                                 NativeMethods.SetWindowPosFlags.IgnoreMove |
                                                                                                 NativeMethods.SetWindowPosFlags.DoNotReposition |
                                                                                                 NativeMethods.SetWindowPosFlags.IgnoreResize |
-                                                                                                NativeMethods.SetWindowPosFlags.HideWindow);                        
-                        this.CurrentMailItem.SetUserProperty(MailItemExtensions.USER_PROPERTY_KEY_INSPECTOR_CLOSED, true, Outlook.OlUserPropertyType.olText);
+                                                                                                NativeMethods.SetWindowPosFlags.HideWindow);
+                        this.CurrentMailItem.SetProcessingState(MailItemExtensions.ProcessingState.ProcessInBackground);
                     }
 
                     // Set pEp options if needed