Я собираю надстройку C# VSTO для Outlook, чтобы обеспечить функциональность одной из тех папок типа «Все почтовые ящики», которые Microsoft использовала раньше. Идея состоит в том, чтобы собрать все из >= 2 почтовых ящиков и объединить их в созданную мной папку «Все почтовые ящики». Однако у меня возникли проблемы с отслеживанием удаленных электронных писем по идентификатору записи — это связано с моей идеей «двустороннего» прослушивания, чтобы электронные письма, удаленные в исходных почтовых ящиках или папке «Все почтовые ящики», также удаляли и другие.
В моем файле AddIn.cs в его нынешнем виде есть несколько упоминаний о «буфере удаления», но я двигаюсь в направлении отказа от этого понятия буферизации в случае, если пользователь массовое удаление писем.
Часть кода, которая сейчас закомментирована:
allMailboxesItems.ItemRemove += new Outlook.ItemsEvents_ItemRemoveEventHandler(OnItemRemovedFromAllMailboxes);
...нуждается в перекалибровке для достижения целей, о которых я упоминал ранее. Папка «Все почтовые ящики», которая является конечным продуктом этой надстройки, должна отслеживать удаления из исходных папок, чтобы она могла соответствующим образом обновляться, и наоборот.
Все, что происходит в одна папка должна присутствовать и в другой.
Вот файл AddIn.cs, в котором все методы собраны вместе, пока я не перейду к этапу их дифференциации, группировки и разделения. . Пожалуйста, простите за наличие некоторых посторонних методов и переменных.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using Outlook = Microsoft.Office.Interop.Outlook;
using Office = Microsoft.Office.Core;
using Microsoft.Office.Interop.Outlook;
using System.IO;
using Newtonsoft.Json;
using System.Windows.Forms;
using System.Diagnostics;
using System.ComponentModel;
using System.Threading.Tasks;
namespace AllMailboxes
{
public partial class ThisAddIn
{
private System.Timers.Timer duplicateRemovalTimer;
private string jsonFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "names.json");
private List emailGroupNames;
private List inboxItemsList = new List();
private HashSet processedEmailIds = new HashSet();
//****^^^^ can delete processed var ? did the copiedEmail... HashSet totally take over its function?
private HashSet copiedEmailInventory = new HashSet();
private Dictionary origEmails = new Dictionary();
//^^used with LinkCopyUniqueStringTo.. method to have connection b/w original emails and
private Dictionary uniqueStringToEntryId = new Dictionary();
private List buffer = new List();
private Queue deleteBuffer = new Queue();
private Timer deleteBufferTimer;
private Timer srcDeleteBufferTimer;
private Outlook.Folder allMailboxesFolder;
private void ThisAddIn_Startup(object sender, System.EventArgs e)
{
Outlook.Application outlookApp = this.Application;
Outlook.NameSpace outlookNamespace = outlookApp.GetNamespace("MAPI");
DateTime filterDateTime = DateTime.Now.AddDays(-7);
LoadEmailGroupNamesFromJson();
var primaryInbox = emailGroupNames.FirstOrDefault();
Outlook.Folder primaryMailbox = outlookApp.Session.Folders[primaryInbox] as Outlook.Folder;
allMailboxesFolder = GetUnifiedFolder(outlookApp);
if (allMailboxesFolder == null)
{
allMailboxesFolder = primaryMailbox.Folders.Add("All Mailboxes", Outlook.OlDefaultFolders.olFolderInbox) as Outlook.Folder;
}
//^^create the All Mailboxes folder if it doesn't exist yet
AllMailboxesInventory();
foreach (var mailBoxName in emailGroupNames)
{
try
{
Outlook.Folder mailbox = outlookApp.Session.Folders[mailBoxName] as Outlook.Folder;
if (mailbox != null)
{
Outlook.Folder inboxFolder = mailbox.Folders["Inbox"] as Outlook.Folder;
Outlook.Items inboxItems = inboxFolder.Items;
string filterDate = filterDateTime.ToString("MM/dd/yyyy");
string filter = $"[ReceivedTime] >= '{filterDate}'";
Outlook.Items mailItems = inboxFolder.Items;
Outlook.Items filteredMailItems = mailItems.Restrict(filter);
foreach (object item in filteredMailItems)
//this is probably where duplicate emails are getting added - modify this to
//do the unique email linkage instead (dictionary with = **original
//email's (straight from source inbox) entry id, = unique string
//combination that can be taken from any folder, original or copy
{
if (item is Outlook.MailItem mailItem)
{
try
{
Outlook.MailItem itemCopy = item as Outlook.MailItem;
string uniqueStr = UniqueStr(itemCopy);
if (!copiedEmailInventory.Contains(uniqueStr))
//^^makes sure it's not pre-existing
{
copiedEmailInventory.Add(uniqueStr);
mailItem.Copy().Move(allMailboxesFolder);
BufferAndSaveMapping(uniqueStr, itemCopy.EntryID);
TrackOriginalEmail(itemCopy);
//^^only when it has checked the uniqueness tracking values
//does it queue it up to be written to the txt tracking
//file -- there is a system in place to prevent duplicates
//in the persistent txt tracking file
}
}
catch (System.Exception ex)
{
System.Diagnostics.Debug.WriteLine($"Error moving email: {ex.Message}");
}
}
}
}
}
catch (System.Exception ex)
{
MessageBox.Show($"Error accessing folders: {ex.Message}","Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
InitializeInboxListeners(outlookApp);
ScanForDuplicateEmails(filterDateTime);
CleanUpAllMailboxes(filterDateTime);
StartDuplicateRemovalTimer(filterDateTime);
}
private void ThisAddIn_Shutdown(object sender, System.EventArgs e)
{
// Note: Outlook no longer raises this event. If you have code that
// must run when Outlook shuts down, see https://go.microsoft.com/fwlink/?LinkId=506785
FlushBufferToFile();
}
private void StartDuplicateRemovalTimer(DateTime filterDateTime)
{
duplicateRemovalTimer = new System.Timers.Timer(300000); // Every hour
duplicateRemovalTimer.Elapsed += (sender, e) => ScanForDuplicateEmails(filterDateTime);
duplicateRemovalTimer.Start();
}
//following method is for linking emails in All Mailboxes with their source inboxes
private void TrackOriginalEmail(Outlook.MailItem origMailItem)
{
origEmails[UniqueStr(origMailItem)] = origMailItem.EntryID;
}
//initializeinboxlisteners is to sync when new emails are received in source inboxes
private void InitializeInboxListeners(Outlook.Application outlookApp)
{
Outlook.Folder allMailboxesFolder = GetUnifiedFolder(outlookApp);
foreach (var mailboxName in emailGroupNames)
{
try
{
Outlook.Folder mailbox = Application.Session.Folders[mailboxName] as Outlook.Folder;
if (mailbox != null)
{
Outlook.Folder inboxFolder = mailbox.Folders["Inbox"] as Outlook.Folder;
//get items collection and add to list to keep reference
Outlook.Items inboxItems = inboxFolder.Items;
inboxItemsList.Add(inboxItems);
//attach event handler for new items - need async here !
inboxItems.ItemAdd += new Outlook.ItemsEvents_ItemAddEventHandler((object item) =>
{
if (item is Outlook.MailItem newMailItem)
{
try
{
string entryId = TryGetEntryIdWithRetries(newMailItem, 3, 500);
if (entryId != null && !processedEmailIds.Contains(entryId))
{
processedEmailIds.Add(entryId);
copiedEmailInventory.Add(UniqueStr(newMailItem));
origEmails[UniqueStr(newMailItem)] = newMailItem.EntryID;
Outlook.MailItem copiedMailItem = newMailItem.Copy().Move(allMailboxesFolder);
}
} catch (System.Exception ex)
{
MessageBox.Show($"error copying: {ex.Message}", "****ERR****", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
});
}
} catch (System.Exception ex)
{
System.Diagnostics.Debug.WriteLine($"Error setting up inbox listeners for {mailboxName}: {ex.Message}");
}
}
}
private string TryGetEntryIdWithRetries(Outlook.MailItem mailItem, int retryCount, int delayMilliseconds)
{
for (int i = 0; i < retryCount; i++)
{
try
{
return mailItem.EntryID;
}
catch
{
System.Threading.Thread.Sleep(delayMilliseconds);
}
}
System.Diagnostics.Debug.WriteLine("EntryID could not be accessed after retries.");
return null;
}
private void ConfirmOrigEmailAndCopiedEmailLinked(Outlook.MailItem origEmail, Outlook.MailItem copiedEmail)
{
if (!origEmails.ContainsValue(origEmail.EntryID))
{
TrackOriginalEmail(origEmail);
}
}
private void ScanForDuplicateEmails(DateTime filterDateTime)
{
HashSet uniqueEmails = new HashSet();
Dictionary removedVals = new Dictionary();
foreach (Outlook.MailItem mailItem in allMailboxesFolder.Items)
{
try
{
string emailKey = UniqueStr(mailItem);
//^^Entry IDs are NOT unique for copied emails! this is the best solution for a uniqueness check
//that DOES factor in being copied
if (uniqueEmails.Contains(emailKey))
{
mailItem.Delete();
}
else
{
uniqueEmails.Add(emailKey);
}
} catch (System.Exception ex)
{
System.Diagnostics.Debug.WriteLine($"Error processing email: {ex.Message}");
}
}
if (removedVals.Count != 0)
{
string prefix = "...";
foreach (KeyValuePair kvp in removedVals)
{
string lastTen = kvp.Key.Length > 10 ? kvp.Key.Substring(kvp.Key.Length - 10) : kvp.Key;
string res = prefix + lastTen;
Console.WriteLine(res);
}
}
}
private void CleanUpAllMailboxes(DateTime cutoff)
{
HashSet uniqueEmails = new HashSet();
int deletedCount = 0;
for (int i = allMailboxesFolder.Items.Count; i > 0; i--)
{
try
{
Outlook.MailItem mailItem = allMailboxesFolder.Items as Outlook.MailItem;
if (mailItem != null)
{
string emailKey = UniqueStr(mailItem);
if (uniqueEmails.Contains(emailKey))
{
mailItem.Delete();
deletedCount++;
continue;
}
else
{
uniqueEmails.Add(emailKey);
}
if (mailItem.ReceivedTime < cutoff)
{
mailItem.Delete();
deletedCount++;
}
}
}
catch (System.Exception ex)
{
System.Diagnostics.Debug.WriteLine($"Error processing email: {ex.Message}");
}
}
Console.WriteLine($"Cleanup complete. {deletedCount} emails deleted.");
}
//persistent method for tracking emails follows
private void CopyEmailsAndTrackSource(Outlook.Folder sourceInbox)
{
foreach (object item in sourceInbox.Items)
{
if (item is Outlook.MailItem mailItem)
{
try
{
string uniqueString = UniqueStr(mailItem);
Outlook.MailItem copiedMailItem = mailItem.Copy().Move(allMailboxesFolder);
} catch (System.Exception ex)
{
Console.WriteLine($"Error encountered: {ex.Message}");
}
}
}
}
private void BufferAndSaveMapping(string uniqueString, string entryId)
{
buffer.Add($"{uniqueString},{entryId}");
if (buffer.Count >= 100)
{
FlushBufferToFile();
}
}
private void FlushBufferToFile()
{
using (var writer = System.IO.File.AppendText("UniqueStringToEntryIDMap.txt"))
{
foreach (var line in buffer)
{
writer.WriteLine(line);
}
}
buffer.Clear(); // Clear the buffer after writing
}
private void SaveAllMappingsToFile(Dictionary uniqueStringEntryId)
{
using (var writer = new System.IO.StreamWriter("UniqueStringToEntryIDMap.txt", append: true))
{
foreach (var entry in uniqueStringEntryId)
{
writer.WriteLine($"{entry.Key},{entry.Value}");
}
}
}
private void LoadMappingFromFile()
{
if (System.IO.File.Exists("UniqueStringToEntryIDMap.txt"))
{
foreach (var line in System.IO.File.ReadAllLines("UniqueStringToEntryIDMap.txt"))
{
var parts = line.Split(',');
if (parts.Length == 2)
{
uniqueStringToEntryId[parts[0]] = parts[1];
}
}
}
}
//private void SetUpAllMailboxesDeleteListener()
//{
// Outlook.Items allMailboxesItems = allMailboxesFolder.Items;
// allMailboxesItems.ItemRemove += new Outlook.ItemsEvents_ItemRemoveEventHandler(OnItemRemovedFromAllMailboxes);
// deleteBufferTimer = new Timer(500);
// deleteBufferTimer.AutoReset = false;
// deleteBufferTimer.Elapsed += ProcessDeleteBufferQueue;
//}
//private void OnItemRemovedFromAllMailboxes()
//{
// System.Threading.Tasks.Task.Delay(200).ContinueWith(t =>
// {
// string uniqueString = UniqueStr()
// })
//}
//^^11132024 WHERE I'M TRYING TO GET A UNIQUE MAILITEM - THE PRECEDING 2 METHODS
//(SETUPALLMAILBOXES... AND ONITEMREMOVED...
//private void SetUpSourceMailboxesDeleteListener(List otherFolders)
// //^^set up iterating through all source folders here, or do it when
// //the method is invoked in a loop?
//{
// foreach (Outlook.Folder otherFolder in otherFolders)
// {
// Outlook.Items otherFolderItems = otherFolder.Items;
// otherFolderItems.ItemRemove += new Outlook.ItemsEvents_ItemRemoveEventHandler(OnItemRemovedFromOtherFolder);
// srcDeleteBufferTimer = new Timer(500);
// srcDeleteBufferTimer.AutoReset = false;
// srcDeleteBufferTimer.Elapsed += ProcessDeleteBufferQueue;
// }
//}
//private void SetUpAllMailboxesDeleteListener()
//{
// allMailboxesFolder.Items.ItemRemove += new Outlook.ItemsEvents_ItemRemoveEventHandler(OnItemRemovedFromAllMailboxes);
//}
//^^11132024 listener for all mailboxes folder AND its source folders (2 way listening)
private void AllMailboxesInventory()
{
foreach(Outlook.MailItem mailItem in allMailboxesFolder.Items)
{
if (!copiedEmailInventory.Contains(UniqueStr(mailItem)))
{
copiedEmailInventory.Add(UniqueStr(mailItem));
}
}
}
private string UniqueStr(Outlook.MailItem mailItem)
{
return $"{mailItem.Subject}-{mailItem.ReceivedTime}-{mailItem.SenderEmailAddress}".ToUpper(); ;
}
private void LoadEmailGroupNamesFromJson()
{
if ( (File.Exists(jsonFilePath))) {
var jsonContent = File.ReadAllText(jsonFilePath);
emailGroupNames = JsonConvert.DeserializeObject(jsonContent);
}
else {
emailGroupNames = new List { "[email protected]", "ADH PhyNews", "ADH Communications" };
SaveEmailGroupNamesToJson();
}
}
private void SaveEmailGroupNamesToJson()
{
var jsonContent = JsonConvert.SerializeObject(emailGroupNames, Formatting.Indented);
File.WriteAllText(jsonFilePath, jsonContent);
}
private void CreateContextMenuForAllMailboxes()
{
var allMailboxesFolder = Application.Session.Folders["All Mailboxes"] as Outlook.Folder;
if (allMailboxesFolder == null) return;
//allMailboxesFolder.BeforeFolderAdd +=
}
public void ShowPopupToEditEmailGroupNames()
{
using (var popupForm = new EmailGroupEditorForm(emailGroupNames))
{
if (popupForm.ShowDialog() == DialogResult.OK)
{
emailGroupNames = popupForm.UpdatedEmailGroupNames;
SaveEmailGroupNamesToJson();
MessageBox.Show("Email group names updated successfully.");
}
}
}
private Outlook.Folder GetUnifiedFolder(Outlook.Application outlookApp)
{
var primaryInbox = emailGroupNames.FirstOrDefault();
Outlook.Folder primaryMailbox = outlookApp.Session.Folders[primaryInbox] as Outlook.Folder;
//^^sets All Mailboxes folder into user's personal email inbox (the 1st entry in json file)
return primaryMailbox.Folders["All Mailboxes"] as Outlook.Folder;
}
#region VSTO generated code
///
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
///
private void InternalStartup()
{
this.Startup += new System.EventHandler(ThisAddIn_Startup);
this.Shutdown += new System.EventHandler(ThisAddIn_Shutdown);
}
#endregion
}
}
Подробнее здесь: https://stackoverflow.com/questions/791 ... n-tracking
Надстройка взаимодействия с C# Outlook, отслеживание удаления электронной почты VSTO ⇐ C#
-
- Похожие темы
- Ответы
- Просмотры
- Последнее сообщение