/// <summary> /// Download MailContent for given uids. /// </summary> /// <param name="cancel"></param> /// <param name="uids"></param> /// <returns></returns> private async Task RunDownloadContentAsyncLoop(CancellationToken cancel, List <string> uids) { if (uids == null || uids.Count == 0) { return; } // Create mail client. IMailClient client = (new TrivialMailDllFactory()).Build(_serverType); try { await Task.Factory.StartNew(() => { client.Connect(_serverEncryption, _host); client.Login(_user, _password); // If is not connected or encrypted (if req) then will break and try to reconnect. if (client.IsConnected && (_serverEncryption == MailServerEncryption.Unencrypted || client.IsEncrypted)) { using (IMailStorage <MailContentEntity> storage = _mailContentStorageFactory()) { foreach (var uid in uids) { if (string.IsNullOrWhiteSpace(uid)) { continue; } if (cancel.IsCancellationRequested) { break; } var downloadRequired = true; MailContentEntity mailContentEntity = null; if (storage.Exists(x => x.Uid == uid)) { mailContentEntity = storage.FindOne(x => x.Uid == uid); if (mailContentEntity != null && mailContentEntity.IsComplete) { downloadRequired = false; } } if (downloadRequired) { // 1. Insert empty MailContent with only uid set to prevent other concurrent method to download same content. mailContentEntity = new MailContentEntity() { Uid = uid, IsComplete = false }; storage.Insert(new List <MailContentEntity>() { mailContentEntity }); // 2. Download complete email. var message = client.GetMessageByUid(uid); // Note: MailDll documentation states that message can be null. if (message != null) { IMail email = new MailBuilder().CreateFromEml(message); if (email != null) { // 3. Update database with downloaded email content. mailContentEntity = new MailContentEntity() { Uid = uid, Date = email?.Date == null ? DateTime.MinValue : email.Date.Value, Html = email.Html, MessageId = email.MessageID, Text = email.Text, CustomHeader = email?.Document.Root.Headers["x-spam"], IsComplete = true }; storage.Update(new List <MailContentEntity>() { mailContentEntity }); // Publish MailContent. _mailContentStream.OnNext(new MailContent(mailContentEntity)); } } } } } } }, cancel, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default); } catch (Exception e) { Logger.Error(e, $"RunDownloadContentAsync"); } finally { client?.Close(); } }
/// <summary> /// Every 30s queries mail server for new email. /// When there are new emails available it first download all mail headers and publishes them to the stream. /// Afterwards start downloading all mail content for just downloaded mail headers. /// </summary> /// <param name="cancel"></param> /// <returns></returns> private async Task RunCheckForNewMailAsyncLoop(CancellationToken cancel) { // Create mail client. IMailClient client = (new TrivialMailDllFactory()).Build(_serverType); try { // Publish Connecting state. _controllerStateStream.OnNext(ControllerState.Connecting); client.Connect(_serverEncryption, _host); // Publish LoggingIn state. _controllerStateStream.OnNext(ControllerState.LoggingIn); client.Login(_user, _password); // Publish Connected state. _controllerStateStream.OnNext(ControllerState.Connected); // Main loop while (!cancel.IsCancellationRequested) { // If disconnect or not encrypted (when should be) then reconnect. if (client.IsConnected && (_serverEncryption == MailServerEncryption.Unencrypted || client.IsEncrypted)) { // MailHeaderList contains new headers which will be published to subscribers. List <MailHeaderEntity> mailHeaderEntities = new List <MailHeaderEntity>(); using (IMailStorage <MailHeaderEntity> storage = _mailHeaderStorageFactory()) { // 1. Get from mail server all uids (emails). // ToDo: for Imap this could be improved. List <string> newUids = client.GetAllUids().ToList(); // 2. Distinct list of uids which are not yet stored in the database. // Let's reverse and start checking with the most recent email (the latest uid). newUids.Reverse(); List <string> uidList = new List <string>(); foreach (var uid in newUids) { if (!storage.Exists(x => x.Uid == uid)) { uidList.Add(uid); } else { break; } // Note: if any first exists, break the loop other emails are probably downloaded. } if (uidList.Count > 0) { // 3. Download mail headers. foreach (var uid in uidList) { // Download message header. var header = client.GetHeadersByUid(uid); // Note: MailDll documentation states that header can be null. if (header == null) { throw new ArgumentNullException(nameof(header), $"Downloaded an empty email header ({uid})."); } var email = new MailBuilder().CreateFromEml(header); var emailFrom = email?.From.FirstOrDefault(); var mailHeaderEntity = new MailHeaderEntity() { Uid = uid, Date = email?.Date ?? DateTime.MinValue, Subject = email?.Subject, MailFromEntity = new MailFromEntity() { Address = emailFrom?.Address, Name = emailFrom?.Name, LocalPart = emailFrom?.LocalPart, DomainPart = emailFrom?.DomainPart } }; mailHeaderEntities.Add(mailHeaderEntity); } // 4. Insert all new mail headers into the storage. storage.Insert(mailHeaderEntities); } } // For all new email headers publish them to the subscribers and download the content. // Note: This whole block is taken out from above using() to release storage handle asap. if (mailHeaderEntities.Count > 0) { // 5. Publish all new mail headers to the stream. mailHeaderEntities.ForEach(mailHeaderEntity => { _mailHeaderStream.OnNext(new MailHeader(mailHeaderEntity)); }); // 6. Start downloading content loop // It's not done in above foreach loop to not to keep storage open for too long // when running over slow internet connection. RunDownloadContentAsyncLoop(cancel, mailHeaderEntities.Select(x => x.Uid).ToList()); } } else { break; } // Check for new email again in 30s await Observable.Return(0).Delay(TimeSpan.FromSeconds(30), Scheduler.CurrentThread).ToTask(cancel); } } catch (Exception e) { Logger.Error(e, $"RunCheckForNewMailAsyncLoop"); } finally { client?.Close(); if (!cancel.IsCancellationRequested) { // Publish Disconnected state. _controllerStateStream.OnNext(ControllerState.Disconnected); } } }