/// <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); } } }