diff --git a/Wino.Core.Domain/Interfaces/AfterRequestExecutionSynchronizationInterfaces.cs b/Wino.Core.Domain/Interfaces/ICustomFolderSynchronizationRequest.cs
similarity index 100%
rename from Wino.Core.Domain/Interfaces/AfterRequestExecutionSynchronizationInterfaces.cs
rename to Wino.Core.Domain/Interfaces/ICustomFolderSynchronizationRequest.cs
diff --git a/Wino.Core.Domain/Models/MailItem/DraftPreparationRequest.cs b/Wino.Core.Domain/Models/MailItem/DraftPreparationRequest.cs
index 8d7809b6..af8bf6b1 100644
--- a/Wino.Core.Domain/Models/MailItem/DraftPreparationRequest.cs
+++ b/Wino.Core.Domain/Models/MailItem/DraftPreparationRequest.cs
@@ -2,13 +2,18 @@
using System.Text.Json.Serialization;
using MimeKit;
using Wino.Core.Domain.Entities;
+using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Extensions;
namespace Wino.Core.Domain.Models.MailItem;
public class DraftPreparationRequest
{
- public DraftPreparationRequest(MailAccount account, MailCopy createdLocalDraftCopy, string base64EncodedMimeMessage, MailCopy referenceMailCopy = null)
+ public DraftPreparationRequest(MailAccount account,
+ MailCopy createdLocalDraftCopy,
+ string base64EncodedMimeMessage,
+ DraftCreationReason reason,
+ MailCopy referenceMailCopy = null)
{
Account = account ?? throw new ArgumentNullException(nameof(account));
@@ -19,6 +24,7 @@ public class DraftPreparationRequest
// This is additional work when deserialization needed, but not much to do atm.
Base64LocalDraftMimeMessage = base64EncodedMimeMessage;
+ Reason = reason;
}
[JsonConstructor]
@@ -29,6 +35,7 @@ public class DraftPreparationRequest
public MailCopy ReferenceMailCopy { get; set; }
public string Base64LocalDraftMimeMessage { get; set; }
+ public DraftCreationReason Reason { get; set; }
[JsonIgnore]
private MimeMessage createdLocalDraftMimeMessage;
@@ -44,5 +51,5 @@ public class DraftPreparationRequest
}
}
- public MailAccount Account { get; }
+ public MailAccount Account { get; set; }
}
diff --git a/Wino.Core.Domain/Models/Requests/ServerRequestPackage.cs b/Wino.Core.Domain/Models/Requests/ServerRequestPackage.cs
index 98ca5792..31a408a2 100644
--- a/Wino.Core.Domain/Models/Requests/ServerRequestPackage.cs
+++ b/Wino.Core.Domain/Models/Requests/ServerRequestPackage.cs
@@ -3,14 +3,11 @@ using Wino.Core.Domain.Interfaces;
namespace Wino.Core.Domain.Models.Requests
{
-
///
/// Encapsulates request to queue and account for synchronizer.
///
- ///
- ///
+ /// Which account to execute this request for.
/// Prepared request for the server.
- /// Whihc account to execute this request for.
public record ServerRequestPackage(Guid AccountId, IRequestBase Request) : IClientMessage
{
public override string ToString() => $"Server Package: {Request.GetType().Name}";
diff --git a/Wino.Core/Extensions/OutlookIntegratorExtensions.cs b/Wino.Core/Extensions/OutlookIntegratorExtensions.cs
index 269bd55f..12e635c4 100644
--- a/Wino.Core/Extensions/OutlookIntegratorExtensions.cs
+++ b/Wino.Core/Extensions/OutlookIntegratorExtensions.cs
@@ -66,7 +66,7 @@ namespace Wino.Core.Extensions
return mailCopy;
}
- public static Message AsOutlookMessage(this MimeMessage mime, string threadId)
+ public static Message AsOutlookMessage(this MimeMessage mime, bool includeInternetHeaders)
{
var fromAddress = GetRecipients(mime.From).ElementAt(0);
var toAddresses = GetRecipients(mime.To).ToList();
@@ -85,13 +85,19 @@ namespace Wino.Core.Extensions
CcRecipients = ccAddresses,
BccRecipients = bccAddresses,
From = fromAddress,
- InternetMessageId = GetMessageIdHeader(mime.MessageId),
- ConversationId = threadId,
- InternetMessageHeaders = GetHeaderList(mime),
+ InternetMessageId = GetProperId(mime.MessageId),
ReplyTo = replyToAddresses,
Attachments = []
};
+ // Headers are only included when creating the draft.
+ // When sending, they are not included. Graph will throw an error.
+
+ if (includeInternetHeaders)
+ {
+ message.InternetMessageHeaders = GetHeaderList(mime);
+ }
+
foreach (var part in mime.BodyParts)
{
if (part.IsAttachment)
@@ -172,7 +178,10 @@ namespace Wino.Core.Extensions
// Here we'll try to ignore some headers that are not neccessary.
// Outlook API will generate them automatically.
+ // Some headers also require to start with X- or x-.
+
string[] headersToIgnore = ["Date", "To", "MIME-Version", "From", "Subject", "Message-Id"];
+ string[] headersToModify = ["In-Reply-To", "Reply-To", "References", "Thread-Topic"];
var headers = new List();
@@ -182,7 +191,15 @@ namespace Wino.Core.Extensions
{
if (!headersToIgnore.Contains(header.Field))
{
- headers.Add(new InternetMessageHeader() { Name = header.Field, Value = header.Value });
+ if (headersToModify.Contains(header.Field))
+ {
+ headers.Add(new InternetMessageHeader() { Name = $"X-{header.Field}", Value = header.Value });
+ }
+ else
+ {
+ headers.Add(new InternetMessageHeader() { Name = header.Field, Value = header.Value });
+ }
+
includedHeaderCount++;
}
@@ -192,15 +209,15 @@ namespace Wino.Core.Extensions
return headers;
}
- private static string GetMessageIdHeader(string messageId)
+ private static string GetProperId(string id)
{
- // Message-Id header must always start with "X-" or "x-".
- if (string.IsNullOrEmpty(messageId)) return string.Empty;
+ // Outlook requires some identifiers to start with "X-" or "x-".
+ if (string.IsNullOrEmpty(id)) return string.Empty;
- if (!messageId.StartsWith("x-") || !messageId.StartsWith("X-"))
- return $"X-{messageId}";
+ if (!id.StartsWith("x-") || !id.StartsWith("X-"))
+ return $"X-{id}";
- return messageId;
+ return id;
}
#endregion
}
diff --git a/Wino.Core/Synchronizers/OutlookSynchronizer.cs b/Wino.Core/Synchronizers/OutlookSynchronizer.cs
index d2591118..7b2c0054 100644
--- a/Wino.Core/Synchronizers/OutlookSynchronizer.cs
+++ b/Wino.Core/Synchronizers/OutlookSynchronizer.cs
@@ -5,12 +5,10 @@ using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
-using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Graph;
-using Microsoft.Graph.Me.SendMail;
using Microsoft.Graph.Models;
using Microsoft.Kiota.Abstractions;
using Microsoft.Kiota.Abstractions.Authentication;
@@ -604,22 +602,50 @@ namespace Wino.Core.Synchronizers
{
if (item is CreateDraftRequest createDraftRequest)
{
- createDraftRequest.DraftPreperationRequest.CreatedLocalDraftMimeMessage.Prepare(EncodingConstraint.None);
+ var reason = createDraftRequest.DraftPreperationRequest.Reason;
+ var message = createDraftRequest.DraftPreperationRequest.CreatedLocalDraftMimeMessage.AsOutlookMessage(true);
- var plainTextBytes = Encoding.UTF8.GetBytes(createDraftRequest.DraftPreperationRequest.CreatedLocalDraftMimeMessage.ToString());
- var base64Encoded = Convert.ToBase64String(plainTextBytes);
+ if (reason == DraftCreationReason.Empty)
+ {
+ return _graphClient.Me.Messages.ToPostRequestInformation(message);
+ }
+ else if (reason == DraftCreationReason.Reply)
+ {
+ return _graphClient.Me.Messages[createDraftRequest.DraftPreperationRequest.ReferenceMailCopy.Id].CreateReply.ToPostRequestInformation(new Microsoft.Graph.Me.Messages.Item.CreateReply.CreateReplyPostRequestBody()
+ {
+ Message = message
+ });
+ }
+ else if (reason == DraftCreationReason.ReplyAll)
+ {
+ return _graphClient.Me.Messages[createDraftRequest.DraftPreperationRequest.ReferenceMailCopy.Id].CreateReplyAll.ToPostRequestInformation(new Microsoft.Graph.Me.Messages.Item.CreateReplyAll.CreateReplyAllPostRequestBody()
+ {
+ Message = message
+ });
+ }
+ else if (reason == DraftCreationReason.Forward)
+ {
+ return _graphClient.Me.Messages[createDraftRequest.DraftPreperationRequest.ReferenceMailCopy.Id].CreateForward.ToPostRequestInformation(new Microsoft.Graph.Me.Messages.Item.CreateForward.CreateForwardPostRequestBody()
+ {
+ Message = message
+ });
+ //createDraftRequest.DraftPreperationRequest.CreatedLocalDraftMimeMessage.Prepare(EncodingConstraint.None);
- var requestInformation = _graphClient.Me.Messages.ToPostRequestInformation(new Message());
+ //var plainTextBytes = Encoding.UTF8.GetBytes(createDraftRequest.DraftPreperationRequest.CreatedLocalDraftMimeMessage.ToString());
+ //var base64Encoded = Convert.ToBase64String(plainTextBytes);
- requestInformation.Headers.Clear();// replace the json content header
- requestInformation.Headers.Add("Content-Type", "text/plain");
+ //var requestInformation = _graphClient.Me.Messages.ToPostRequestInformation(new Message());
- requestInformation.SetStreamContent(new MemoryStream(Encoding.UTF8.GetBytes(base64Encoded)), "text/plain");
+ //requestInformation.Headers.Clear();// replace the json content header
+ //requestInformation.Headers.Add("Content-Type", "text/plain");
- return requestInformation;
+ //requestInformation.SetStreamContent(new MemoryStream(Encoding.UTF8.GetBytes(base64Encoded)), "text/plain");
+
+ //return requestInformation;
+ }
}
- return default;
+ throw new Exception("Invalid create draft request type.");
});
}
@@ -646,16 +672,29 @@ namespace Wino.Core.Synchronizers
// Alias support is lacking with direct MIMEs.
// Therefore we convert the MIME message to Outlook message and use proper APIs.
- var outlookMessage = mimeMessage.AsOutlookMessage(sendDraftPreparationRequest.MailItem.ThreadId);
- var sendMailPostRequest = _graphClient.Me.SendMail.ToPostRequestInformation(new SendMailPostRequestBody()
- {
- Message = outlookMessage
- });
+ // sendDraftPreparationRequest.MailItem.ThreadId
+ var outlookMessage = mimeMessage.AsOutlookMessage(false);
- var sendMailRequest = new HttpRequestBundle(sendMailPostRequest, request);
+ // Update draft.
- return [deleteBundle, sendMailRequest];
+ var patchDraftRequest = _graphClient.Me.Messages[mailCopyId].ToPatchRequestInformation(outlookMessage);
+ var patchDraftRequestBundle = new HttpRequestBundle(patchDraftRequest, request);
+
+ // Send draft.
+
+ var sendDraftRequest = _graphClient.Me.Messages[mailCopyId].Send.ToPostRequestInformation();
+ var sendDraftRequestBundle = new HttpRequestBundle(sendDraftRequest, request);
+
+ //var sendMailPostRequest = _graphClient.Me.SendMail.ToPostRequestInformation(new SendMailPostRequestBody()
+ //{
+ // Message = outlookMessage
+ //});
+
+ //var sendMailRequest = new HttpRequestBundle(sendMailPostRequest, request);
+
+ return [sendDraftRequestBundle];
+ //return [deleteBundle, sendMailRequest];
}
public override IEnumerable> Archive(BatchArchiveRequest request)
@@ -712,17 +751,32 @@ namespace Wino.Core.Synchronizers
request.ApplyUIChanges();
- await batchContent.AddBatchRequestStepAsync(nativeRequest).ConfigureAwait(false);
+ var batchRequestId = await batchContent.AddBatchRequestStepAsync(nativeRequest).ConfigureAwait(false);
// Map BundleId to batch request step's key.
// This is how we can identify which step succeeded or failed in the bundle.
- bundle.BundleId = batchContent.BatchRequestSteps.ElementAt(i).Key;
+ bundle.BundleId = batchRequestId;//batchContent.BatchRequestSteps.ElementAt(i).Key;
}
if (!batchContent.BatchRequestSteps.Any())
continue;
+ // Set execution type to serial instead of parallel if needed.
+ // Each step will depend on the previous one.
+
+ if (itemCount > 1)
+ {
+ for (int i = 1; i < itemCount; i++)
+ {
+ var currentStep = batchContent.BatchRequestSteps.ElementAt(i);
+ var previousStep = batchContent.BatchRequestSteps.ElementAt(i - 1);
+
+ currentStep.Value.DependsOn = [previousStep.Key];
+ }
+
+ }
+
// Execute batch. This will collect responses from network call for each batch step.
var batchRequestResponse = await _graphClient.Batch.PostAsync(batchContent).ConfigureAwait(false);
@@ -742,6 +796,7 @@ namespace Wino.Core.Synchronizers
var httpResponseMessage = await batchRequestResponse.GetResponseByIdAsync(bundleId);
+ var codes = await batchRequestResponse.GetResponsesStatusCodesAsync();
using (httpResponseMessage)
{
await ProcessSingleNativeRequestResponseAsync(bundle, httpResponseMessage, cancellationToken).ConfigureAwait(false);
diff --git a/Wino.Mail.ViewModels/AppShellViewModel.cs b/Wino.Mail.ViewModels/AppShellViewModel.cs
index 4a9ac7cd..0acc62da 100644
--- a/Wino.Mail.ViewModels/AppShellViewModel.cs
+++ b/Wino.Mail.ViewModels/AppShellViewModel.cs
@@ -788,7 +788,7 @@ namespace Wino.Mail.ViewModels
var (draftMailCopy, draftBase64MimeMessage) = await _mailService.CreateDraftAsync(account.Id, draftOptions).ConfigureAwait(false);
- var draftPreparationRequest = new DraftPreparationRequest(account, draftMailCopy, draftBase64MimeMessage);
+ var draftPreparationRequest = new DraftPreparationRequest(account, draftMailCopy, draftBase64MimeMessage, draftOptions.Reason);
await _winoRequestDelegator.ExecuteAsync(draftPreparationRequest);
}
diff --git a/Wino.Mail.ViewModels/MailRenderingPageViewModel.cs b/Wino.Mail.ViewModels/MailRenderingPageViewModel.cs
index 305a7ece..7fe96b2e 100644
--- a/Wino.Mail.ViewModels/MailRenderingPageViewModel.cs
+++ b/Wino.Mail.ViewModels/MailRenderingPageViewModel.cs
@@ -273,7 +273,7 @@ namespace Wino.Mail.ViewModels
var (draftMailCopy, draftBase64MimeMessage) = await _mailService.CreateDraftAsync(initializedMailItemViewModel.AssignedAccount.Id, draftOptions).ConfigureAwait(false);
- var draftPreparationRequest = new DraftPreparationRequest(initializedMailItemViewModel.AssignedAccount, draftMailCopy, draftBase64MimeMessage, initializedMailItemViewModel.MailCopy);
+ var draftPreparationRequest = new DraftPreparationRequest(initializedMailItemViewModel.AssignedAccount, draftMailCopy, draftBase64MimeMessage, draftOptions.Reason, initializedMailItemViewModel.MailCopy);
await _requestDelegator.ExecuteAsync(draftPreparationRequest);