Bug 1498866 - Only delete original template when saving new template, manage auto-saved versions. r=aceman a=jorgk
authorJorg K <jorgk@jorgk.com>
Thu, 18 Oct 2018 09:38:29 +0200
changeset 33059 df0d79b1cb64
parent 33058 1b7d33c8b832
child 33060 9ce6347cab62
push id386
push userclokep@gmail.com
push dateTue, 23 Oct 2018 00:48:12 +0000
reviewersaceman, jorgk
bugs1498866
Bug 1498866 - Only delete original template when saving new template, manage auto-saved versions. r=aceman a=jorgk
mail/components/compose/content/MsgComposeCommands.js
mailnews/compose/public/nsIMsgCompFields.idl
mailnews/compose/src/nsMsgCompFields.cpp
mailnews/compose/src/nsMsgCompFields.h
mailnews/compose/src/nsMsgCompose.cpp
mailnews/compose/src/nsMsgCompose.h
mailnews/mime/src/mimedrft.cpp
--- a/mail/components/compose/content/MsgComposeCommands.js
+++ b/mail/components/compose/content/MsgComposeCommands.js
@@ -4338,16 +4338,19 @@ function ComposeCanClose()
         return false;
       case 1: //Cancel
         return false;
       case 2: //Don't Save
         // don't delete the draft if we didn't start off editing a draft
         // and the user hasn't explicitly saved it.
         if (!gEditingDraft && gAutoSaveKickedIn)
           RemoveDraft();
+        // Remove auto-saved draft created during "edit template".
+        if (gMsgCompose.compFields.templateId && gAutoSaveKickedIn)
+          RemoveDraft();
         break;
     }
   }
 
   return true;
 }
 
 function RemoveDraft()
--- a/mailnews/compose/public/nsIMsgCompFields.idl
+++ b/mailnews/compose/public/nsIMsgCompFields.idl
@@ -34,16 +34,17 @@ interface nsIMsgCompFields : msgIWritabl
   attribute string  references;
   attribute string  priority;
   attribute string  messageId;
   attribute string  characterSet;
   readonly attribute string defaultCharacterSet;
 
   attribute AString templateName;
   attribute string  draftId;
+  attribute string  templateId;
 
   attribute boolean returnReceipt;
   attribute long receiptHeaderType;
   attribute boolean DSN;
   attribute boolean attachVCard;
   attribute boolean forcePlainText;
   attribute boolean useMultipartAlternative;
   attribute boolean bodyIsAsciiOnly;
--- a/mailnews/compose/src/nsMsgCompFields.cpp
+++ b/mailnews/compose/src/nsMsgCompFields.cpp
@@ -45,16 +45,17 @@ static HeaderInfo kHeaders[] = {
   { "Organization", false },
   { "References", true },
   { "X-Mozilla-News-Host", false },
   { "X-Priority", false },
   { nullptr, false }, // CHARACTER_SET
   { "Message-Id", true },
   { "X-Template", true },
   { nullptr, false }, // DRAFT_ID
+  { nullptr, false }, // TEMPLATE_ID
   { "Content-Language", true },
   { nullptr, false } // CREATOR IDENTITY KEY
 };
 
 static_assert(MOZ_ARRAY_LENGTH(kHeaders) ==
     nsMsgCompFields::MSG_MAX_HEADERS,
   "These two arrays need to be kept in sync or bad things will happen!");
 
@@ -355,16 +356,27 @@ NS_IMETHODIMP nsMsgCompFields::SetDraftI
 }
 
 NS_IMETHODIMP nsMsgCompFields::GetDraftId(char **_retval)
 {
   *_retval = strdup(GetAsciiHeader(MSG_DRAFT_ID_HEADER_ID));
   return *_retval ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
 }
 
+NS_IMETHODIMP nsMsgCompFields::SetTemplateId(const char *value)
+{
+  return SetAsciiHeader(MSG_TEMPLATE_ID_HEADER_ID, value);
+}
+
+NS_IMETHODIMP nsMsgCompFields::GetTemplateId(char **_retval)
+{
+  *_retval = strdup(GetAsciiHeader(MSG_TEMPLATE_ID_HEADER_ID));
+  return *_retval ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
+}
+
 NS_IMETHODIMP nsMsgCompFields::SetReturnReceipt(bool value)
 {
   m_returnReceipt = value;
   return NS_OK;
 }
 
 NS_IMETHODIMP nsMsgCompFields::GetReturnReceipt(bool *_retval)
 {
--- a/mailnews/compose/src/nsMsgCompFields.h
+++ b/mailnews/compose/src/nsMsgCompFields.h
@@ -57,16 +57,17 @@ public:
     MSG_ORGANIZATION_HEADER_ID,
     MSG_REFERENCES_HEADER_ID,
     MSG_NEWSPOSTURL_HEADER_ID,
     MSG_PRIORITY_HEADER_ID,
     MSG_CHARACTER_SET_HEADER_ID,
     MSG_MESSAGE_ID_HEADER_ID,
     MSG_X_TEMPLATE_HEADER_ID,
     MSG_DRAFT_ID_HEADER_ID,
+    MSG_TEMPLATE_ID_HEADER_ID,
     MSG_CONTENT_LANGUAGE_ID,
     MSG_CREATOR_IDENTITY_KEY_ID,
 
     MSG_MAX_HEADERS   //Must be the last one.
   } MsgHeaderID;
 
   nsresult SetAsciiHeader(MsgHeaderID header, const char *value);
   const char* GetAsciiHeader(MsgHeaderID header); //just return the address of the internal header variable, don't dispose it
@@ -122,16 +123,17 @@ public:
   const char* GetCharacterSet() {return GetAsciiHeader(MSG_CHARACTER_SET_HEADER_ID);}
 
   const char* GetMessageId() {return GetAsciiHeader(MSG_MESSAGE_ID_HEADER_ID);}
 
   nsresult SetTemplateName(const char *value) {return SetAsciiHeader(MSG_X_TEMPLATE_HEADER_ID, value);}
   const char* GetTemplateName() {return GetAsciiHeader(MSG_X_TEMPLATE_HEADER_ID);}
 
   const char* GetDraftId() {return GetAsciiHeader(MSG_DRAFT_ID_HEADER_ID);}
+  const char* GetTemplateId() {return GetAsciiHeader(MSG_TEMPLATE_ID_HEADER_ID);}
 
   const char* GetContentLanguage() {return GetAsciiHeader(MSG_CONTENT_LANGUAGE_ID);}
 
   bool GetReturnReceipt() {return m_returnReceipt;}
   bool GetDSN() {return m_DSN;}
   bool GetAttachVCard() {return m_attachVCard;}
   bool GetAttachmentReminder() {return m_attachmentReminder;}
   int32_t GetDeliveryFormat() {return m_deliveryFormat;}
--- a/mailnews/compose/src/nsMsgCompose.cpp
+++ b/mailnews/compose/src/nsMsgCompose.cpp
@@ -3725,17 +3725,17 @@ nsresult nsMsgComposeSendListener::OnSto
           msgCompose->CloseWindow();  // if we fail on the simple GetFcc call, close the window to be safe and avoid
                                       // windows hanging around to prevent the app from exiting.
       }
 
       // Remove the current draft msg when sending draft is done.
       bool deleteDraft;
       msgCompose->GetDeleteDraft(&deleteDraft);
       if (deleteDraft)
-        RemoveCurrentDraftMessage(msgCompose, false, true);
+        RemoveCurrentDraftMessage(msgCompose, false, false);
     }
     else
     {
       msgCompose->NotifyStateListeners(nsIMsgComposeNotificationType::ComposeProcessDone, aStatus);
       if (progress)
       {
         progress->CloseProgressDialog(true);
         progress->UnregisterListener(this);
@@ -3809,26 +3809,26 @@ nsMsgComposeSendListener::OnStopCopy(nsr
       // We should only close the window if we are done. Things like templates
       // and drafts aren't done so their windows should stay open
       if (mDeliverMode == nsIMsgSend::nsMsgSaveAsDraft ||
           mDeliverMode == nsIMsgSend::nsMsgSaveAsTemplate)
       {
         msgCompose->NotifyStateListeners(nsIMsgComposeNotificationType::SaveInFolderDone, aStatus);
         // Remove the current draft msg when saving as draft/template is done.
         msgCompose->SetDeleteDraft(true);
-        RemoveCurrentDraftMessage(msgCompose, true, false);
+        RemoveCurrentDraftMessage(msgCompose, true, mDeliverMode == nsIMsgSend::nsMsgSaveAsTemplate);
       }
       else
       {
         // Remove (possible) draft if we're in send later mode
         if (mDeliverMode == nsIMsgSend::nsMsgQueueForLater ||
             mDeliverMode == nsIMsgSend::nsMsgDeliverBackground)
         {
           msgCompose->SetDeleteDraft(true);
-          RemoveCurrentDraftMessage(msgCompose, true, true);
+          RemoveCurrentDraftMessage(msgCompose, true, false);
         }
         msgCompose->CloseWindow();
       }
     }
     msgCompose->ClearMessageSend();
   }
 
   return rv;
@@ -3852,137 +3852,163 @@ nsMsgComposeSendListener::GetMsgFolder(n
 
   nsCOMPtr<nsIMsgFolder> folder = do_QueryInterface(resource, &rv);
   NS_ENSURE_SUCCESS(rv, rv);
   folder.forget(msgFolder);
   return rv;
 }
 
 nsresult
-nsMsgComposeSendListener::RemoveCurrentDraftMessage(nsIMsgCompose *compObj, bool calledByCopy, bool isSend)
+nsMsgComposeSendListener::RemoveDraftOrTemplate(nsIMsgCompose *compObj, nsCString msgURI, bool isSaveTemplate)
+{
+  nsresult rv;
+  nsCOMPtr<nsIMsgFolder> msgFolder;
+  nsCOMPtr<nsIMsgDBHdr> msgDBHdr;
+  rv = GetMsgDBHdrFromURI(msgURI.get(), getter_AddRefs(msgDBHdr));
+  NS_ASSERTION(NS_SUCCEEDED(rv), "RemoveDraftOrTemplate can't get msg header DB interface pointer");
+  if (NS_SUCCEEDED(rv) && msgDBHdr)
+  {
+    do { // Break on failure or removal not needed.
+      // Get the folder for the message resource.
+      rv = msgDBHdr->GetFolder(getter_AddRefs(msgFolder));
+      NS_ASSERTION(NS_SUCCEEDED(rv), "RemoveDraftOrTemplate can't get msg folder interface pointer");
+      if (NS_FAILED(rv) || !msgFolder)
+        break;
+
+      // Only do this if it's a drafts or templates folder.
+      uint32_t flags;
+      msgFolder->GetFlags(&flags);
+      if (!(flags & (nsMsgFolderFlags::Drafts | nsMsgFolderFlags::Templates)))
+        break;
+      // Only delete a template when saving a new one, never delete a template when sending.
+      if (!isSaveTemplate && (flags & nsMsgFolderFlags::Templates))
+        break;
+
+      // Only remove if the message is actually in the db. It might have only
+      // been in the use cache.
+      nsMsgKey key;
+      rv = msgDBHdr->GetMessageKey(&key);
+      if (NS_FAILED(rv))
+        break;
+      nsCOMPtr<nsIMsgDatabase> db;
+      msgFolder->GetMsgDatabase(getter_AddRefs(db));
+      if (!db)
+        break;
+      bool containsKey = false;
+      db->ContainsKey(key, &containsKey);
+      if (!containsKey)
+        break;
+
+      // Build the msg array.
+      nsCOMPtr<nsIMutableArray> messageArray(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv));
+      NS_ASSERTION(NS_SUCCEEDED(rv), "RemoveDraftOrTemplate can't allocate array");
+      if (NS_FAILED(rv) || !messageArray)
+        break;
+      rv = messageArray->AppendElement(msgDBHdr);
+      NS_ASSERTION(NS_SUCCEEDED(rv), "RemoveDraftOrTemplate can't append msg header to array");
+      if (NS_FAILED(rv))
+        break;
+
+      // Ready to delete the msg.
+      rv = msgFolder->DeleteMessages(messageArray, nullptr, true, false, nullptr, false /*allowUndo*/);
+      NS_ASSERTION(NS_SUCCEEDED(rv), "RemoveDraftOrTemplate can't delete message");
+    } while(false);
+  }
+  else
+  {
+    // If we get here we have the case where the draft folder is on the server and
+    // it's not currently open (in thread pane), so draft msgs are saved to the server
+    // but they're not in our local DB. In this case, GetMsgDBHdrFromURI() will never
+    // find the msg. If the draft folder is a local one then we'll not get here because
+    // the draft msgs are saved to the local folder and are in local DB. Make sure the
+    // msg folder is imap.  Even if we get here due to DB errors (worst case), we should
+    // still try to delete msg on the server because that's where the master copy of the
+    // msgs are stored, if draft folder is on the server.
+    // For local case, since DB is bad we can't do anything with it anyway so it'll be
+    // noop in this case.
+    rv = GetMsgFolder(compObj, getter_AddRefs(msgFolder));
+    if (NS_SUCCEEDED(rv) && msgFolder)
+    {
+      nsCOMPtr <nsIMsgImapMailFolder> imapFolder = do_QueryInterface(msgFolder);
+      NS_ASSERTION(imapFolder, "The draft folder MUST be an imap folder in order to mark the msg delete!");
+      if (NS_SUCCEEDED(rv) && imapFolder)
+      {
+        // Only do this if it's a drafts or templates folder.
+        uint32_t flags;
+        msgFolder->GetFlags(&flags);
+        if (!(flags & (nsMsgFolderFlags::Drafts | nsMsgFolderFlags::Templates)))
+          return NS_OK;
+        // Only delete a template when saving a new one, never delete a template when sending.
+        if (!isSaveTemplate && (flags & nsMsgFolderFlags::Templates))
+          return NS_OK;
+
+        const char * str = PL_strchr(msgURI.get(), '#');
+        NS_ASSERTION(str, "Failed to get current draft id url");
+        if (str)
+        {
+          nsAutoCString srcStr(str+1);
+          nsresult err;
+          nsMsgKey messageID = srcStr.ToInteger(&err);
+          if (messageID != nsMsgKey_None)
+          {
+            rv = imapFolder->StoreImapFlags(kImapMsgDeletedFlag, true,
+                                            &messageID, 1, nullptr);
+          }
+        }
+      }
+    }
+  }
+
+  return rv;
+}
+
+/**
+ * Remove the current draft message since a new one will be saved.
+ * When we're coming to save a template, also delete the original template.
+ * This is necessary since auto-save doesn't delete the original template.
+ */
+nsresult
+nsMsgComposeSendListener::RemoveCurrentDraftMessage(nsIMsgCompose *compObj, bool calledByCopy, bool isSaveTemplate)
 {
   nsresult rv;
   nsCOMPtr <nsIMsgCompFields> compFields = nullptr;
 
   rv = compObj->GetCompFields(getter_AddRefs(compFields));
   NS_ASSERTION(NS_SUCCEEDED(rv), "RemoveCurrentDraftMessage can't get compose fields");
   if (NS_FAILED(rv) || !compFields)
     return rv;
 
   nsCString curDraftIdURL;
-  nsMsgKey newUid = 0;
-  nsCString newDraftIdURL;
-  nsCOMPtr<nsIMsgFolder> msgFolder;
-
   rv = compFields->GetDraftId(getter_Copies(curDraftIdURL));
   NS_ASSERTION(NS_SUCCEEDED(rv), "RemoveCurrentDraftMessage can't get draft id");
 
   // Skip if no draft id (probably a new draft msg).
-  if (NS_SUCCEEDED(rv) && !curDraftIdURL.IsEmpty())
-  {
-    nsCOMPtr <nsIMsgDBHdr> msgDBHdr;
-    rv = GetMsgDBHdrFromURI(curDraftIdURL.get(), getter_AddRefs(msgDBHdr));
-    NS_ASSERTION(NS_SUCCEEDED(rv), "RemoveCurrentDraftMessage can't get msg header DB interface pointer.");
-    if (NS_SUCCEEDED(rv) && msgDBHdr)
-    {
-      do { // Break on failure or removal not needed.
-        // Get the folder for the message resource.
-        rv = msgDBHdr->GetFolder(getter_AddRefs(msgFolder));
-        NS_ASSERTION(NS_SUCCEEDED(rv), "RemoveCurrentDraftMessage can't get msg folder interface pointer.");
-        if (NS_FAILED(rv) || !msgFolder)
-          break;
-
-        // Only do this if it's a drafts or templates folder.
-        uint32_t flags;
-        msgFolder->GetFlags(&flags);
-        if (!(flags & (nsMsgFolderFlags::Drafts | nsMsgFolderFlags::Templates)))
-          break;
-        // Never delete a template when sending.
-        if (isSend && (flags & nsMsgFolderFlags::Templates))
-          break;
-
-        // Only remove if the message is actually in the db. It might have only
-        // been in the use cache.
-        nsMsgKey key;
-        rv = msgDBHdr->GetMessageKey(&key);
-        if (NS_FAILED(rv))
-          break;
-        nsCOMPtr<nsIMsgDatabase> db;
-        msgFolder->GetMsgDatabase(getter_AddRefs(db));
-        if (!db)
-          break;
-        bool containsKey = false;
-        db->ContainsKey(key, &containsKey);
-        if (!containsKey)
-          break;
-
-        // Build the msg array.
-        nsCOMPtr<nsIMutableArray> messageArray(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv));
-        NS_ASSERTION(NS_SUCCEEDED(rv), "RemoveCurrentDraftMessage can't allocate array.");
-        if (NS_FAILED(rv) || !messageArray)
-          break;
-        rv = messageArray->AppendElement(msgDBHdr);
-        NS_ASSERTION(NS_SUCCEEDED(rv), "RemoveCurrentDraftMessage can't append msg header to array.");
-        if (NS_FAILED(rv))
-          break;
-
-        // Ready to delete the msg.
-        rv = msgFolder->DeleteMessages(messageArray, nullptr, true, false, nullptr, false /*allowUndo*/);
-        NS_ASSERTION(NS_SUCCEEDED(rv), "RemoveCurrentDraftMessage can't delete message.");
-      } while(false);
-    }
-    else
-    {
-      // If we get here we have the case where the draft folder
-      // is on the server and
-      // it's not currently open (in thread pane), so draft
-      // msgs are saved to the server
-      // but they're not in our local DB. In this case,
-      // GetMsgDBHdrFromURI() will never
-      // find the msg. If the draft folder is a local one
-      // then we'll not get here because
-      // the draft msgs are saved to the local folder and
-      // are in local DB. Make sure the
-      // msg folder is imap.  Even if we get here due to
-      // DB errors (worst case), we should
-      // still try to delete msg on the server because
-      // that's where the master copy of the
-      // msgs are stored, if draft folder is on the server.
-      // For local case, since DB is bad
-      // we can't do anything with it anyway so it'll be
-      // noop in this case.
-      rv = GetMsgFolder(compObj, getter_AddRefs(msgFolder));
-      if (NS_SUCCEEDED(rv) && msgFolder)
-      {
-        nsCOMPtr <nsIMsgImapMailFolder> imapFolder = do_QueryInterface(msgFolder);
-        NS_ASSERTION(imapFolder, "The draft folder MUST be an imap folder in order to mark the msg delete!");
-        if (NS_SUCCEEDED(rv) && imapFolder)
-        {
-          const char * str = PL_strchr(curDraftIdURL.get(), '#');
-          NS_ASSERTION(str, "Failed to get current draft id url");
-          if (str)
-          {
-            nsAutoCString srcStr(str+1);
-            nsresult err;
-            nsMsgKey messageID = srcStr.ToInteger(&err);
-            if (messageID != nsMsgKey_None)
-            {
-              rv = imapFolder->StoreImapFlags(kImapMsgDeletedFlag, true,
-                                              &messageID, 1, nullptr);
-            }
-          }
-        }
-      }
+  if (NS_SUCCEEDED(rv) && !curDraftIdURL.IsEmpty()) {
+    rv = RemoveDraftOrTemplate(compObj, curDraftIdURL, isSaveTemplate);
+    if (NS_FAILED(rv))
+      NS_WARNING("Removing current draft failed");
+  }
+
+  if (isSaveTemplate) {
+    nsCString templateIdURL;
+    rv = compFields->GetTemplateId(getter_Copies(templateIdURL));
+    if (NS_SUCCEEDED(rv) && !templateIdURL.Equals(curDraftIdURL)) {
+      // Above we deleted an auto-saved draft, so here we need to delete
+      // the original template.
+      rv = RemoveDraftOrTemplate(compObj, templateIdURL, isSaveTemplate);
+      if (NS_FAILED(rv))
+        NS_WARNING("Removing original template failed");
     }
   }
 
   // Now get the new uid so that next save will remove the right msg
   // regardless whether or not the exiting msg can be deleted.
   if (calledByCopy)
   {
+    nsMsgKey newUid = 0;
     nsCOMPtr<nsIMsgFolder> savedToFolder;
     nsCOMPtr<nsIMsgSend> msgSend;
     rv = compObj->GetMessageSend(getter_AddRefs(msgSend));
     NS_ASSERTION(msgSend, "RemoveCurrentDraftMessage msgSend is null.");
     if (NS_FAILED(rv) || !msgSend)
       return rv;
 
     rv = msgSend->GetMessageKey(&newUid);
@@ -3993,19 +4019,22 @@ nsMsgComposeSendListener::RemoveCurrentD
 
     // Reset draft (uid) url with the new uid.
     if (savedToFolder && newUid != nsMsgKey_None)
     {
       uint32_t folderFlags;
       savedToFolder->GetFlags(&folderFlags);
       if (folderFlags & (nsMsgFolderFlags::Drafts | nsMsgFolderFlags::Templates))
       {
+        nsCString newDraftIdURL;
         rv = savedToFolder->GenerateMessageURI(newUid, newDraftIdURL);
         NS_ENSURE_SUCCESS(rv, rv);
         compFields->SetDraftId(newDraftIdURL.get());
+        if (isSaveTemplate)
+          compFields->SetTemplateId(newDraftIdURL.get());
       }
     }
   }
   return rv;
 }
 
 nsresult
 nsMsgComposeSendListener::SetMessageKey(nsMsgKey aMessageKey)
--- a/mailnews/compose/src/nsMsgCompose.h
+++ b/mailnews/compose/src/nsMsgCompose.h
@@ -214,17 +214,18 @@ public:
   NS_DECL_NSIMSGSENDLISTENER
 
   // nsIMsgCopyServiceListener interface
   NS_DECL_NSIMSGCOPYSERVICELISTENER
 
   // nsIWebProgressListener interface
   NS_DECL_NSIWEBPROGRESSLISTENER
 
-  nsresult    RemoveCurrentDraftMessage(nsIMsgCompose *compObj, bool calledByCopy, bool isSend);
+  nsresult    RemoveDraftOrTemplate(nsIMsgCompose *compObj, nsCString msgURI, bool isSaveTemplate);
+  nsresult    RemoveCurrentDraftMessage(nsIMsgCompose *compObj, bool calledByCopy, bool isSaveTemplate);
   nsresult    GetMsgFolder(nsIMsgCompose *compObj, nsIMsgFolder **msgFolder);
 
 private:
   virtual ~nsMsgComposeSendListener();
   nsWeakPtr               mWeakComposeObj;
   MSG_DeliverMode         mDeliverMode;
 };
 
--- a/mailnews/mime/src/mimedrft.cpp
+++ b/mailnews/mime/src/mimedrft.cpp
@@ -1681,18 +1681,20 @@ mime_parse_stream_complete(nsMIMESession
       // the message store.
       //
       if (mdd->format_out == nsMimeOutput::nsMimeMessageEditorTemplate)
       {
         // Set the draft ID when editing a template so the original is
         // overwritten when saving the template again.
         // Note that always setting the draft ID here would cause drafts to be
         // overwritten when edited "as new", which is undesired.
-        if (msgComposeType == nsIMsgCompType::EditTemplate)
+        if (msgComposeType == nsIMsgCompType::EditTemplate) {
           fields->SetDraftId(mdd->url_name);
+          fields->SetTemplateId(mdd->url_name);  // Remember original template ID.
+        }
 
         if (convertToPlainText)
           fields->ConvertBodyToPlainText();
 
         CreateTheComposeWindow(fields, newAttachData, msgComposeType,
                                composeFormat, mdd->identity,
                                mdd->originalMsgURI, mdd->origMsgHdr);
       }