Skip to content

Commit e1ca883

Browse files
committed
added SaveTemplateAsync and WithTemplateAsync
1 parent 529fceb commit e1ca883

File tree

18 files changed

+424
-153
lines changed

18 files changed

+424
-153
lines changed

samples/.dockerignore

Lines changed: 0 additions & 25 deletions
This file was deleted.

samples/WorkerServiceExample/Worker.cs

Lines changed: 72 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
using MailKitSimplified.Receiver.Services;
77
using MailKitSimplified.Sender.Abstractions;
88
using System.Diagnostics;
9+
using System.Collections.Generic;
10+
using CommunityToolkit.Common;
911

1012
namespace ExampleNamespace;
1113

@@ -14,12 +16,14 @@ public class Worker : BackgroundService
1416
private readonly ILogger<Worker> _logger;
1517
private readonly ISmtpSender _smtpSender;
1618
private readonly IImapReceiver _imapReceiver;
19+
private readonly ILoggerFactory _loggerFactory;
1720

18-
public Worker(ISmtpSender smtpSender, IImapReceiver imapReceiver, ILogger<Worker> logger)
21+
public Worker(ISmtpSender smtpSender, IImapReceiver imapReceiver, ILoggerFactory loggerFactory)
1922
{
20-
_logger = logger;
23+
_logger = loggerFactory.CreateLogger<Worker>();
2124
_smtpSender = smtpSender;
2225
_imapReceiver = imapReceiver;
26+
_loggerFactory = loggerFactory;
2327
}
2428

2529
protected override async Task ExecuteAsync(CancellationToken cancellationToken = default)
@@ -29,16 +33,29 @@ protected override async Task ExecuteAsync(CancellationToken cancellationToken =
2933
//await ReceiveAsync(cancellationToken);
3034
//await QueryAsync(cancellationToken);
3135
//await MonitorAsync(cancellationToken);
32-
await DeleteSeenAsync(cancellationTokenSource);
33-
await NotReentrantAsync(cancellationToken);
36+
//await DeleteSeenAsync(cancellationTokenSource);
37+
//await NotReentrantAsync(cancellationToken);
38+
//await SendAttachmentAsync(500);
39+
//await DownloadAllAttachmentsAsync(cancellationToken);
40+
await TemplateSendAsync();
41+
}
42+
43+
private async Task DownloadAllAttachmentsAsync(CancellationToken cancellationToken = default)
44+
{
45+
var mimeMessage = await GetNewestMimeMessageAsync(cancellationToken);
46+
string downloadFolder = Path.GetFullPath("Downloads");
47+
_logger.LogInformation($"Downloading attachments from {mimeMessage?.MessageId} into {downloadFolder}.");
48+
var downloads = await MimeMessageReader.Create(mimeMessage).SetLogger(_loggerFactory)
49+
.DownloadAllAttachmentsAsync(downloadFolder, createDirectory: true);
50+
_logger.LogInformation($"Downloads ({downloads.Count}): {downloads.ToEnumeratedString()}.");
3451
}
3552

3653
private async Task MoveSeenToSentAsync(CancellationTokenSource cancellationTokenSource)
3754
{
3855
var filteredMessages = await _imapReceiver.ReadMail.Query(SearchQuery.Seen)
3956
.GetMessageSummariesAsync(cancellationTokenSource.Token);
4057
_logger.LogInformation($"{_imapReceiver} folder query returned {filteredMessages.Count} messages.");
41-
var sentFolder = ((MailFolderClient)_imapReceiver.MailFolderClient).SentFolder.Value;
58+
var sentFolder = _imapReceiver.MailFolderClient.SentFolder.Value;
4259
var messagesDeleted = await _imapReceiver.MailFolderClient
4360
.MoveToAsync(filteredMessages.Select(m => m.UniqueId), sentFolder, cancellationTokenSource.Token);
4461
_logger.LogInformation($"Deleted {messagesDeleted} messages from {_imapReceiver} {filteredMessages.Count} Seen messages.");
@@ -58,7 +75,7 @@ private async Task NotReentrantAsync(CancellationToken cancellationToken = defau
5875
{
5976
using var cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
6077
var sendTask = DelayedSendAsync(500, cancellationToken);
61-
var newestEmail = await GetNewestMessageSummaryAsync(cancellationToken);
78+
var newestEmail = await GetNewestMessageSummaryAsync();
6279
await _imapReceiver.MonitorFolder.SetMessageSummaryItems()
6380
.SetIgnoreExistingMailOnConnect()
6481
.OnMessageArrival(OnArrivalAsync)
@@ -100,15 +117,27 @@ private async Task ForwardOnArrivalAsync(IMessageSummary messageSummary)
100117
//_smtpSender.Enqueue(mimeForward);
101118
}
102119

103-
public async Task<IMessageSummary?> GetNewestMessageSummaryAsync(CancellationToken cancellationToken = default)
120+
public async Task<MimeMessage?> GetNewestMimeMessageAsync(CancellationToken cancellationToken = default)
104121
{
105122
using var mailFolderClient = _imapReceiver.MailFolderClient;
123+
var messageSummary = await GetNewestMessageSummaryAsync(mailFolderClient, cancellationToken);
124+
var mimeMessage = await messageSummary.GetMimeMessageAsync(cancellationToken);
125+
return mimeMessage;
126+
}
127+
128+
public async Task<IMessageSummary?> GetNewestMessageSummaryAsync(IMailFolderClient? mailFolderClient = null, CancellationToken cancellationToken = default)
129+
{
130+
bool dispose = mailFolderClient == null;
131+
mailFolderClient ??= _imapReceiver.MailFolderClient;
106132
var mailFolder = await mailFolderClient.ConnectAsync(false, cancellationToken).ConfigureAwait(false);
107133
var index = mailFolder.Count > 0 ? mailFolder.Count - 1 : mailFolder.Count;
108134
var filter = MessageSummaryItems.UniqueId;
109135
var messageSummaries = await mailFolder.FetchAsync(index, index, filter, cancellationToken).ConfigureAwait(false);
110136
await mailFolder.CloseAsync(false, CancellationToken.None).ConfigureAwait(false);
111-
return messageSummaries.FirstOrDefault();
137+
var messageSummary = messageSummaries.FirstOrDefault();
138+
if (dispose)
139+
await mailFolderClient.DisposeAsync();
140+
return messageSummary;
112141
}
113142

114143
private async Task GetMessageSummaryRepliesAsync(CancellationToken cancellationToken = default)
@@ -166,15 +195,45 @@ private async Task QueryAsync(CancellationToken cancellationToken = default)
166195
_logger.LogInformation($"{_imapReceiver} received {messageSummaries.Count} email(s) in {stopwatch.Elapsed.TotalSeconds:n1}s: {messageSummaries.Select(m => m.UniqueId).ToEnumeratedString()}.");
167196
}
168197

169-
private async Task DelayedSendAsync(int millisecondsDelay, CancellationToken cancellationToken = default)
198+
private IEmailWriter GetTemplate(string from = "me@localhost")
170199
{
171-
await Task.Delay(millisecondsDelay, cancellationToken);
172-
var id = $"{Guid.NewGuid():N}";
173-
bool isSent = await _smtpSender.WriteEmail
174-
.From("me@localhost")
200+
if (!from.IsEmail())
201+
_logger.LogWarning($"{from} is not a valid email.");
202+
var id = $"{Guid.NewGuid():N}"[..8];
203+
var template = _smtpSender.WriteEmail
204+
.From(from)
175205
.To($"{id}@localhost")
176206
.Subject(id)
177207
.BodyText("text/plain.")
208+
.SaveTemplate();
209+
return template;
210+
}
211+
212+
private async Task TemplateSendAsync(CancellationToken cancellationToken = default)
213+
{
214+
//var template = await GetTemplate().SaveTemplateAsync();
215+
//var template = await _smtpSender.WithTemplateAsync();
216+
var template = GetTemplate();
217+
bool isSent = await template.TrySendAsync(cancellationToken);
218+
_logger.LogInformation($"Email {(isSent ? "sent" : "failed to send")}.");
219+
isSent = await template.TrySendAsync(cancellationToken);
220+
_logger.LogInformation($"Email {(isSent ? "sent" : "failed to send")}.");
221+
}
222+
223+
private async Task SendAttachmentAsync(int millisecondsDelay, string filePath = "..\\..\\README.md", CancellationToken cancellationToken = default)
224+
{
225+
bool isSent = await GetTemplate()
226+
.TryAttach(filePath)
227+
.TrySendAsync(cancellationToken);
228+
_logger.LogInformation($"Email {(isSent ? "sent" : "failed to send")}.");
229+
await Task.Delay(millisecondsDelay, cancellationToken);
230+
}
231+
232+
private async Task DelayedSendAsync(int millisecondsDelay, CancellationToken cancellationToken = default)
233+
{
234+
await Task.Delay(millisecondsDelay, cancellationToken);
235+
var id = $"{Guid.NewGuid():N}"[..8];
236+
bool isSent = await GetTemplate()
178237
.TrySendAsync(cancellationToken);
179238
_logger.LogInformation($"Email {(isSent ? "sent" : "failed to send")}.");
180239
}

samples/WorkerServiceExample/WorkerServiceExample.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
</PropertyGroup>
99

1010
<ItemGroup>
11+
<PackageReference Include="CommunityToolkit.Common" Version="8.2.1" />
1112
<PackageReference Include="Microsoft.Extensions.Hosting" Version="7.0.1" />
1213
</ItemGroup>
1314

source/MailKitSimplified.Receiver/Abstractions/IMailFolderClient.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ namespace MailKitSimplified.Receiver.Abstractions
1010
{
1111
public interface IMailFolderClient : IAsyncDisposable, IDisposable
1212
{
13+
Lazy<IMailFolder> SentFolder { get; }
14+
1315
/// <summary>
1416
/// Connect to the configured mail folder.
1517
/// </summary>

source/MailKitSimplified.Receiver/Extensions/AttachmentExtensions.cs

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,31 +38,49 @@ public static IEnumerable<MimeEntity> GetFilteredAttachments(this IEnumerable<Mi
3838
mimeEntities.Where(a => a.IsAttachment && a is MimePart att && fileTypeSuffix.Any(s =>
3939
att.FileName?.EndsWith(s, StringComparison.OrdinalIgnoreCase) ?? false));
4040

41-
public static async Task<MemoryStream> GetMimeEntityStream(this MimeEntity mimeEntity, CancellationToken ct = default)
41+
public static async Task<MemoryStream> GetMimeEntityStream(this MimeEntity mimeEntity, CancellationToken cancellationToken = default)
4242
{
4343
var memoryStream = new MemoryStream();
4444
if (mimeEntity != null)
45-
await mimeEntity.WriteToStreamAsync(memoryStream, ct);
45+
await mimeEntity.WriteToStreamAsync(memoryStream, cancellationToken);
4646
memoryStream.Position = 0;
4747
return memoryStream;
4848
}
4949

50-
public static async Task<Stream> WriteToStreamAsync(this MimeEntity entity, Stream stream, CancellationToken ct = default)
50+
public static async Task<Stream> WriteToStreamAsync(this MimeEntity entity, Stream stream, CancellationToken cancellationToken = default)
5151
{
5252
if (entity != null && stream != null)
5353
{
5454
if (entity is MessagePart messagePart)
5555
{
56-
await messagePart.Message.WriteToAsync(stream, ct);
56+
await messagePart.Message.WriteToAsync(stream, cancellationToken);
5757
}
5858
else if (entity is MimePart mimePart && mimePart.Content != null)
5959
{
60-
await mimePart.Content.DecodeToAsync(stream, ct);
60+
await mimePart.Content.DecodeToAsync(stream, cancellationToken);
6161
}
6262
// rewind the stream so the next process can read it from the beginning
6363
stream.Position = 0;
6464
}
6565
return stream;
6666
}
67+
68+
public static async Task<MemoryStream> WriteToStreamAsync(this MimeEntity mimeEntity, CancellationToken cancellationToken = default)
69+
{
70+
var memoryStream = new MemoryStream();
71+
if (mimeEntity != null)
72+
await mimeEntity.WriteToAsync(memoryStream, cancellationToken);
73+
memoryStream.Position = 0;
74+
return memoryStream;
75+
}
76+
77+
public static async Task<MemoryStream> WriteToStreamAsync(this MimeMessage mimeMessage, CancellationToken cancellationToken = default)
78+
{
79+
var memoryStream = new MemoryStream();
80+
if (mimeMessage != null)
81+
await mimeMessage.WriteToAsync(memoryStream, cancellationToken);
82+
memoryStream.Position = 0;
83+
return memoryStream;
84+
}
6785
}
6886
}

source/MailKitSimplified.Receiver/Extensions/MimeMessageExtensions.cs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
using MimeKit;
2+
using MimeKit.IO;
23
using MimeKit.Text;
34
using System;
45
using System.IO;
56
using System.Text;
67
using System.Linq;
78
using System.Threading;
9+
using System.Threading.Tasks;
810
using System.Collections.Generic;
911
using System.Diagnostics.CodeAnalysis;
12+
using MailKitSimplified.Receiver.Services;
1013

1114
namespace MailKitSimplified.Receiver.Extensions
1215
{
@@ -284,5 +287,24 @@ internal static string QuoteForReply(this MimeMessage original, string message,
284287
var result = stringBuilder.ToString();
285288
return result;
286289
}
290+
291+
internal static async Task<MimeMessage> CloneStreamReferencesAsync(this MimeMessage mimeMessage, bool persistent, MemoryBlockStream memoryBlockStream = null, CancellationToken cancellationToken = default)
292+
{
293+
if (memoryBlockStream == null)
294+
memoryBlockStream = new MemoryBlockStream();
295+
await mimeMessage.WriteToAsync(memoryBlockStream, cancellationToken);
296+
memoryBlockStream.Position = 0;
297+
var result = await MimeMessage.LoadAsync(memoryBlockStream, persistent, cancellationToken).ConfigureAwait(false);
298+
return result;
299+
}
300+
301+
public static async Task<MimeMessage> CopyAsync(this MimeMessage mimeMessage, CancellationToken cancellationToken = default) =>
302+
await mimeMessage.CloneStreamReferencesAsync(true, null, cancellationToken).ConfigureAwait(false);
303+
304+
public static async Task<MimeMessage> CloneAsync(this MimeMessage mimeMessage, CancellationToken cancellationToken = default) =>
305+
await mimeMessage.CloneStreamReferencesAsync(false, null, cancellationToken).ConfigureAwait(false);
306+
307+
public static MimeMessageReader Read(this MimeMessage mimeMessage, string mailFolderName = null, uint folderIndex = 0) =>
308+
MimeMessageReader.Create(mimeMessage, mailFolderName, folderIndex);
287309
}
288310
}

source/MailKitSimplified.Receiver/MailKitSimplified.Receiver.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@
3030
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="7.0.0" />
3131
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="7.0.0" />
3232
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="7.0.0" />
33+
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="7.0.0" />
34+
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="7.0.0" />
3335
<PackageReference Include="Microsoft.Extensions.Logging" Version="7.0.0" />
3436
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
3537
<PackageReference Include="System.IO.Abstractions" Version="19.2.51" />

source/MailKitSimplified.Receiver/Services/MailFolderReader.cs

Lines changed: 55 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@
33
using MailKit.Search;
44
using MailKit.Net.Imap;
55
using System;
6+
using System.IO;
67
using System.Linq;
78
using System.Threading;
89
using System.Threading.Tasks;
910
using System.Collections.Generic;
11+
using System.Runtime.CompilerServices;
1012
using Microsoft.Extensions.Logging;
1113
using Microsoft.Extensions.Logging.Abstractions;
1214
using MailKitSimplified.Receiver.Abstractions;
@@ -282,30 +284,69 @@ public async Task<IList<MimeMessage>> GetMimeMessagesAsync(IEnumerable<UniqueId>
282284
}
283285
return mimeMessages;
284286
}
287+
#if NET5_0_OR_GREATER
288+
public async IAsyncEnumerable<MimeMessage> GetMimeMessages(IEnumerable<UniqueId> uniqueIds, [EnumeratorCancellation] CancellationToken cancellationToken = default, ITransferProgress progress = null)
289+
{
290+
if (uniqueIds != null)
291+
{
292+
var mailFolder = await _imapReceiver.ConnectMailFolderAsync(cancellationToken).ConfigureAwait(false);
293+
_ = mailFolder.Open(FolderAccess.ReadOnly, cancellationToken);
294+
foreach (var uniqueId in uniqueIds)
295+
{
296+
if (cancellationToken.IsCancellationRequested)
297+
break;
298+
var mimeMessage = await mailFolder.GetMessageAsync(uniqueId, cancellationToken, progress).ConfigureAwait(false);
299+
_logger.LogTrace($"{_imapReceiver} received {mimeMessage.MessageId}.");
300+
if (mimeMessage != null)
301+
yield return mimeMessage;
302+
}
303+
mailFolder.Close(false, CancellationToken.None);
304+
}
305+
}
285306

286-
[Obsolete("Consider using GetMimeMessagesAsync() instead.")]
287-
public IEnumerable<MimeMessage> GetMimeMessages(IEnumerable<UniqueId> uniqueIds, CancellationToken cancellationToken = default, ITransferProgress progress = null)
307+
public async Task SaveAllAsync(IEnumerable<UniqueId> uniqueIds, string folderPath, bool createDirectory = false, CancellationToken cancellationToken = default, ITransferProgress progress = null)
308+
{
309+
if (createDirectory)
310+
Directory.CreateDirectory(folderPath);
311+
else if (!Directory.Exists(folderPath))
312+
throw new DirectoryNotFoundException($"Directory not found: {folderPath}.");
313+
var format = FormatOptions.Default.Clone();
314+
format.NewLineFormat = NewLineFormat.Dos;
315+
await foreach (var mimeMessage in GetMimeMessages(uniqueIds, cancellationToken, progress))
316+
{
317+
string fileName = Path.Combine(folderPath, $"{mimeMessage.MessageId}.eml");
318+
await mimeMessage.WriteToAsync(format, fileName, cancellationToken).ConfigureAwait(false);
319+
}
320+
}
321+
#else
322+
public async Task SaveAllAsync(IEnumerable<UniqueId> uniqueIds, string folderPath, bool createDirectory = false, CancellationToken cancellationToken = default, ITransferProgress progress = null)
288323
{
289324
if (uniqueIds != null)
290325
{
291-
var mailFolder = _imapReceiver.ConnectMailFolderAsync(cancellationToken).GetAwaiter().GetResult();
292-
lock (mailFolder.SyncRoot)
326+
if (createDirectory)
327+
Directory.CreateDirectory(folderPath);
328+
else if (!Directory.Exists(folderPath))
329+
throw new DirectoryNotFoundException($"Directory not found: {folderPath}.");
330+
var format = FormatOptions.Default.Clone();
331+
format.NewLineFormat = NewLineFormat.Dos;
332+
var mailFolder = await _imapReceiver.ConnectMailFolderAsync(cancellationToken).ConfigureAwait(false);
333+
_ = mailFolder.Open(FolderAccess.ReadOnly, cancellationToken);
334+
foreach (var uniqueId in uniqueIds)
293335
{
294-
_ = mailFolder.Open(FolderAccess.ReadOnly, cancellationToken);
295-
foreach (var uniqueId in uniqueIds)
336+
if (cancellationToken.IsCancellationRequested)
337+
break;
338+
var mimeMessage = await mailFolder.GetMessageAsync(uniqueId, cancellationToken, progress).ConfigureAwait(false);
339+
_logger.LogTrace($"{_imapReceiver} received {mimeMessage.MessageId}.");
340+
if (mimeMessage != null)
296341
{
297-
if (cancellationToken.IsCancellationRequested)
298-
break;
299-
var mimeMessage = mailFolder.GetMessage(uniqueId, cancellationToken, progress);
300-
_logger.LogTrace($"{_imapReceiver} received {mimeMessage.MessageId}.");
301-
if (mimeMessage != null)
302-
yield return mimeMessage;
342+
string fileName = Path.Combine(folderPath, $"{mimeMessage.MessageId}.eml");
343+
await mimeMessage.WriteToAsync(format, fileName, cancellationToken).ConfigureAwait(false);
303344
}
304-
mailFolder.Close(false, CancellationToken.None);
305345
}
346+
mailFolder.Close(false, CancellationToken.None);
306347
}
307348
}
308-
349+
#endif
309350
/// <summary>Query just the arrival dates of messages on the server.</summary>
310351
/// <param name="deliveredAfter">Search for messages after this date.</param>
311352
/// <param name="deliveredBefore">Search for messages before this date.</param>

0 commit comments

Comments
 (0)