DecryptionStack.cs
author Thomas
Wed, 06 Jun 2018 15:59:49 +0200
changeset 2230 664d0aef77e2
parent 2180 e43aa424a621
child 2231 897403f5eafe
permissions -rw-r--r--
OUT-462: Reload items with header-only
     1 ´╗┐using System;
     2 using System.Collections.Generic;
     3 using Outlook = Microsoft.Office.Interop.Outlook;
     4 
     5 namespace pEp
     6 {
     7     internal class DecryptionStack : Stack<string>, IDisposable
     8     {
     9         private const int MAX_DECRYPTION_COUNT                       = 5;       // Maximum amount of concurrently decrypted items
    10         private const int DECRYPTION_INTERVAL                        = 500;     // in ms
    11 
    12         private System.Windows.Forms.Timer decryptionTimer           = null;
    13         private int decryptionCounter                                = 0;
    14         private object mutexDecryptionCounter                        = new object();
    15         private object mutexDecryptionStack                          = new object();
    16 
    17         /// <summary>
    18         /// List to store already copied items to prevent unnecessary processing
    19         /// </summary>
    20         private List<string> copiedItemsList = new List<string>();
    21 
    22         /// <summary>
    23         /// Default constructor.
    24         /// </summary>
    25         public DecryptionStack()
    26         {
    27             this.decryptionTimer = new System.Windows.Forms.Timer();
    28             this.decryptionTimer.Interval = DECRYPTION_INTERVAL;
    29             this.decryptionTimer.Tick += this.DecryptionTimer_Tick;
    30         }
    31 
    32         /// <summary>
    33         /// Implementing IDisposable
    34         /// </summary>
    35         public void Dispose()
    36         {
    37             this.decryptionTimer.Dispose();
    38         }
    39 
    40         /// <summary>
    41         /// Increments the decryption counter if maximum has not yet been reached.
    42         /// </summary>
    43         /// <returns>True if counter was encrypted, false if maximum has already been reached.</returns>
    44         private bool IncrementDecryptionCounter()
    45         {
    46             bool incremented = false;
    47 
    48             lock (mutexDecryptionCounter)
    49             {
    50                 if (this.decryptionCounter < MAX_DECRYPTION_COUNT)
    51                 {
    52                     this.decryptionCounter++;
    53                     incremented = true;
    54                     Log.Verbose("DecryptionStack.IncrementDecryptionCounter: counter incremented to " + decryptionCounter);
    55                 }
    56             }
    57 
    58             return incremented;
    59         }
    60 
    61         /// <summary>
    62         /// Decrements the decryption counter if it is bigger than 0.
    63         /// </summary>
    64         public void DecrementDecryptionCounter()
    65         {
    66             lock (mutexDecryptionCounter)
    67             {
    68                 if (this.decryptionCounter > 0)
    69                 {
    70                     this.decryptionCounter--;
    71                     Log.Verbose("DecryptionStack.DecrementDecryptionCounter: counter decremented to " + decryptionCounter);
    72                 }
    73             }
    74         }
    75 
    76         /// <summary>
    77         /// Resets the decryption counter to 0.
    78         /// </summary>
    79         private void ResetDecryptionCounter()
    80         {
    81             lock (mutexDecryptionCounter)
    82             {
    83                 this.decryptionCounter = 0;
    84                 Log.Verbose("DecryptionStack.ResetDecryptionCounter: counter reset");
    85             }
    86         }
    87 
    88         /// <summary>
    89         /// Locks the decryption stack and tries to get its first element.
    90         /// Also checks if the following element(s) are duplicates of the one
    91         /// returned and removes them in this case.
    92         /// </summary>
    93         /// <param name="entryId">The retrieved entryId or an empty string if none was retrieved.</param>
    94         /// <returns>Tue if the operation retrieved successfully an entryId. Otherwise false.</returns>
    95         private bool TryPop(out string entryId)
    96         {
    97             entryId = null;
    98 
    99             lock (mutexDecryptionStack)
   100             {
   101                 try
   102                 {
   103                     if (this.Count > 0)
   104                     {
   105                         entryId = this.Pop();
   106                         Log.Verbose("DecryptionStack.TryPop: retrieved entryId " + entryId);
   107                     }
   108                 }
   109                 catch (Exception ex)
   110                 {
   111                     entryId = string.Empty;
   112                     Log.Error("TryPopDecryptionStack: Error getting first element from stack. " + ex.ToString());
   113                 }
   114 
   115                 // If an entryId could be retrieved, check following item(s)
   116                 if (string.IsNullOrEmpty(entryId) == false)
   117                 {
   118                     do
   119                     {
   120                         if (this.Count > 0)
   121                         {
   122                             string nextEntryId = null;
   123 
   124                             try
   125                             {
   126                                 nextEntryId = this.Peek();
   127                             }
   128                             catch (Exception ex)
   129                             {
   130                                 Log.Error("TryPopDecryptionStack: Error removing duplicates from stack. " + ex.ToString());
   131                             }
   132 
   133                             // If next item equals the retrieved entryId, remove it
   134                             if (string.Equals(entryId, nextEntryId))
   135                             {
   136                                 try
   137                                 {
   138                                     this.Pop();
   139                                 }
   140                                 catch (Exception ex)
   141                                 {
   142                                     Log.Error("TryPopDecryptionStack: Error removing duplicates from stack. " + ex.ToString());
   143                                     break;
   144                                 }
   145                             }
   146                             else
   147                             {
   148                                 break;
   149                             }
   150                         }
   151                         else
   152                         {
   153                             break;
   154                         }
   155 
   156                     } while (true);
   157                 }
   158             }
   159 
   160             return (string.IsNullOrEmpty(entryId) == false);
   161         }
   162 
   163         /// <summary>
   164         /// Locks the decryption stack and tries to push the given entryId.
   165         /// The item is not pushed if the last entryId is the same.
   166         /// </summary>
   167         /// <param name="entryId">The entryId to push to the stack.</param>
   168         /// <returns>True if item was pushed, otherwise false.</returns>
   169         public bool TryPush(string entryId)
   170         {
   171             bool pushed = false;
   172             string currentId = null;
   173 
   174             if (string.IsNullOrEmpty(entryId))
   175             {
   176                 return false;
   177             }
   178 
   179             // Push item to stack if not already on top of it
   180             lock (mutexDecryptionStack)
   181             {
   182                 if (this.Count > 0)
   183                 {
   184                     try
   185                     {
   186                         currentId = this.Peek();
   187                     }
   188                     catch (Exception ex)
   189                     {
   190                         Log.Error("DecryptionStack.TryPush: Error peeking item. " + ex.ToString());
   191                     }
   192                 }
   193 
   194                 if (entryId?.Equals(currentId) == false)
   195                 {
   196                     this.Push(entryId);
   197                     pushed = true;
   198                     Log.Verbose("DecryptionStack.TryPush: pushed entryId " + entryId);
   199                 }
   200             }
   201 
   202             // If item was pushed, enable timer and start decryption
   203             if (pushed &&
   204                 this.decryptionTimer.Enabled == false)
   205             {
   206                 this.decryptionTimer.Start();
   207                 this.DecryptionTimer_Tick(null, null);
   208                 Log.Verbose("DecryptionStack.TryPush: decryptionTimer started");
   209             }
   210 
   211             return pushed;
   212         }
   213 
   214         /// <summary>
   215         /// Event handler for when the decryption timer has elapsed.
   216         /// </summary>
   217         private void DecryptionTimer_Tick(object sender, EventArgs e)
   218         {
   219             Outlook.MailItem omi = null;
   220             Outlook.MailItem omiCopy = null;
   221             string entryId = null;
   222             CryptableMailItem cmi = null;
   223 
   224             // Try to increment the decryption counter
   225             if (this.IncrementDecryptionCounter() == false)
   226             {
   227                 Log.Verbose("DecryptionStack.DecryptionTimer_Tick: decryption queue full");
   228                 return;
   229             }
   230 
   231             // Pop item from the decryption stack
   232             if (this.TryPop(out entryId))
   233             {
   234                 // Get mail item and set process flag to true
   235                 try
   236                 {
   237                     omi = Globals.ThisAddIn.Application.Session.GetItemFromID(entryId);
   238                 }
   239                 catch (Exception ex)
   240                 {
   241                     omi = null;
   242                     Log.Error("DecryptionTimer_Tick: Error getting item from ID. " + ex.ToString());
   243                 }
   244 
   245                 if ((omi != null) &&
   246                     (omi.GetIsPEPEnabled() || omi.GetIsDecryptAlwaysEnabled()))
   247                 {
   248                     /* In Outlook 2010, mails may arrive with download state OlHeaderOnly, which means we have no attachments at this point.
   249                      * Setting the MarkForDownload can lead to Outlook crashing. Therefore, the workaround here is to create a copy of
   250                      * the mail item, which makes Outlook download the full item.
   251                      */
   252                     if ((Globals.OutlookVersion == Globals.Version.Outlook2010) &&
   253                         (omi.GetIsAutoConsume()) &&
   254                         (omi.GetIsInIMAPStore()) &&
   255                         (copiedItemsList.Contains(omi.EntryID) == false))
   256                     {
   257                         try
   258                         {
   259                             omiCopy = omi.Copy();
   260                             omiCopy.UnRead = false;
   261                             omiCopy.SetPEPProperty(MailItemExtensions.PEPProperty.AutoConsume, "yes");
   262                             copiedItemsList.Add(omi.EntryID);
   263                             copiedItemsList.Add(omiCopy.EntryID);
   264                             Log.Verbose("DecryptionStack.DecryptionTimer_Tick: Copy OMI: " + omi.EntryID + " to: " + omiCopy.EntryID);
   265                         }
   266                         catch (Exception ex)
   267                         {
   268                             omiCopy = null;
   269                             Log.Error("DecryptionStack.DecryptionTimer_Tick: Error during copying mailitem: " + ex.ToString());
   270                         }
   271                     }
   272 
   273                     /* Forcefully protected messages need a special treatment as it might be needed to open a
   274                      * handshake dialog, which has to happen from the main thread. The constructors of those 
   275                      * dialogs cannot be called from the background worker. 
   276                      */
   277                     if (omi.GetIsForcefullyProtected())
   278                     {
   279                         Log.Verbose("DecryptionStack.DecryptionTimer_Tick: Force Protection Protocol Message found. Processing...");
   280 
   281 
   282                         FPPMessage fppMessage = new FPPMessage(omiCopy ?? omi);
   283 
   284                         try
   285                         {
   286                             omiCopy?.Delete();
   287                         }
   288                         catch (Exception ex)
   289                         {
   290                             Log.Error("DecryptionStack.DecryptionTimer_Tick: Error during deleting copied FPP mailitem: " + ex.ToString());
   291                         }
   292 
   293                         fppMessage.ProcessIncoming();
   294                         if (omi.GetIsAutoConsume())
   295                         {
   296                             omi.UnRead = false;
   297                         }
   298                     }
   299                     else
   300                     {
   301                         // Process item
   302                         cmi = new CryptableMailItem(omiCopy ?? omi, null, true);
   303 
   304                         /* Note: Make sure to dispose the MailItem after processing (pass true).
   305                          * This is needed as decryption is asynchronous but the MailItem reference still needs to be released.
   306                          * Do NOT use omi or cmi references after this point!
   307                          */
   308                         cmi.StartProcessing(true);
   309                         Log.Verbose("DecryptionStack.DecryptionTimer_Tick: started decryption of item with entryId " + omi?.EntryID);
   310                     }
   311 
   312                     omi = null;
   313                     omiCopy = null;
   314                 }
   315                 else
   316                 {
   317                     // If we don't process, decrement the decryption counter again
   318                     this.DecrementDecryptionCounter();
   319                     Log.Verbose("DecryptionStack.DecryptionTimer_Tick: item will not be processed");
   320                 }
   321             }
   322             else
   323             {
   324                 // If no more item is on the stack, stop the timer and set decryption counter to 0
   325                 if (this.Count <= 0)
   326                 {
   327                     this.ResetDecryptionCounter();
   328                     this.decryptionTimer.Stop();
   329                     Log.Verbose("DecryptionStack.DecryptionTimer_Tick: counter reset and timer stopped");
   330                 }
   331             }
   332         }
   333     }
   334 }