Files
Wino-Mail/Wino.Services/MimeFileService.cs

198 lines
6.5 KiB
C#
Raw Normal View History

2024-04-18 01:44:37 +02:00
using System;
using System.IO;
using System.Linq;
2024-04-18 01:44:37 +02:00
using System.Threading;
using System.Threading.Tasks;
using MimeKit;
using Serilog;
using Wino.Core.Domain.Interfaces;
using Wino.Core.Domain.Models.MailItem;
using Wino.Core.Domain.Models.Reader;
using Wino.Services.Extensions;
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
namespace Wino.Services;
public class MimeFileService : IMimeFileService
2024-04-18 01:44:37 +02:00
{
2025-02-16 11:54:23 +01:00
private readonly INativeAppService _nativeAppService;
private ILogger _logger = Log.ForContext<MimeFileService>();
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
public MimeFileService(INativeAppService nativeAppService)
{
_nativeAppService = nativeAppService;
}
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
public async Task<MimeMessageInformation> GetMimeMessageInformationAsync(Guid fileId, Guid accountId, CancellationToken cancellationToken = default)
{
var resourcePath = await GetMimeResourcePathAsync(accountId, fileId).ConfigureAwait(false);
var mimeFilePath = GetEMLPath(resourcePath);
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
var loadedMimeMessage = await MimeMessage.LoadAsync(mimeFilePath, cancellationToken).ConfigureAwait(false);
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
return new MimeMessageInformation(loadedMimeMessage, resourcePath);
}
2025-02-16 11:54:23 +01:00
public async Task<MimeMessageInformation> GetMimeMessageInformationAsync(byte[] fileBytes, string emlDirectoryPath, CancellationToken cancellationToken = default)
{
var memoryStream = new MemoryStream(fileBytes);
2025-02-16 11:54:23 +01:00
var loadedMimeMessage = await MimeMessage.LoadAsync(memoryStream, cancellationToken).ConfigureAwait(false);
return new MimeMessageInformation(loadedMimeMessage, emlDirectoryPath);
}
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
public async Task<bool> SaveMimeMessageAsync(Guid fileId, MimeMessage mimeMessage, Guid accountId)
{
try
{
2025-02-16 11:54:23 +01:00
var resourcePath = await GetMimeResourcePathAsync(accountId, fileId).ConfigureAwait(false);
var completeFilePath = GetEMLPath(resourcePath);
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
using var fileStream = File.Open(completeFilePath, FileMode.OpenOrCreate);
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
await mimeMessage.WriteToAsync(fileStream).ConfigureAwait(false);
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
return true;
}
catch (Exception ex)
{
_logger.Error(ex, "Could not save mime file for FileId: {FileId}", fileId);
}
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
return false;
}
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
private string GetEMLPath(string resourcePath) => $"{resourcePath}\\mail.eml";
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
public async Task<string> GetMimeResourcePathAsync(Guid accountId, Guid fileId)
{
var mimeFolderPath = await _nativeAppService.GetMimeMessageStoragePath().ConfigureAwait(false);
var mimeDirectory = Path.Combine(mimeFolderPath, accountId.ToString(), fileId.ToString());
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
if (!Directory.Exists(mimeDirectory))
Directory.CreateDirectory(mimeDirectory);
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
return mimeDirectory;
}
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
public async Task<bool> IsMimeExistAsync(Guid accountId, Guid fileId)
{
var resourcePath = await GetMimeResourcePathAsync(accountId, fileId);
var completeFilePath = GetEMLPath(resourcePath);
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
return File.Exists(completeFilePath);
}
2025-02-16 11:54:23 +01:00
public HtmlPreviewVisitor CreateHTMLPreviewVisitor(MimeMessage message, string mimeLocalPath)
{
var visitor = new HtmlPreviewVisitor(mimeLocalPath);
2025-02-16 11:54:23 +01:00
message.Accept(visitor);
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
// TODO: Match cid with attachments if any.
2025-02-16 11:54:23 +01:00
return visitor;
}
2025-02-16 11:54:23 +01:00
public async Task<bool> DeleteMimeMessageAsync(Guid accountId, Guid fileId)
{
var resourcePath = await GetMimeResourcePathAsync(accountId, fileId);
var completeFilePath = GetEMLPath(resourcePath);
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
if (File.Exists(completeFilePath))
{
try
{
File.Delete(completeFilePath);
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
_logger.Information("Mime file deleted for {FileId}", fileId);
2025-02-16 11:54:23 +01:00
return true;
}
catch (Exception ex)
{
_logger.Error(ex, "Could not delete mime file for {FileId}", fileId);
2024-04-18 01:44:37 +02:00
}
2025-02-16 11:54:23 +01:00
return false;
2024-04-18 01:44:37 +02:00
}
2025-02-16 11:54:23 +01:00
return true;
}
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
public MailRenderModel GetMailRenderModel(MimeMessage message, string mimeLocalPath, MailRenderingOptions options = null)
{
var visitor = CreateHTMLPreviewVisitor(message, mimeLocalPath);
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
string finalRenderHtml = visitor.HtmlBody;
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
// Check whether we need to purify the generated HTML from visitor.
// No need to create HtmlDocument if not required.
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
if (options != null && options.IsPurifyingNeeded())
{
var document = new HtmlAgilityPack.HtmlDocument();
document.LoadHtml(visitor.HtmlBody);
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
// Clear <img> src attribute.
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
if (!options.LoadImages)
document.ClearImages();
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
if (!options.LoadStyles)
document.ClearStyles();
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
// Update final HTML.
finalRenderHtml = document.DocumentNode.OuterHtml;
}
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
var renderingModel = new MailRenderModel(finalRenderHtml, options);
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
// Create attachments.
2024-04-18 01:44:37 +02:00
2025-02-16 11:54:23 +01:00
foreach (var attachment in visitor.Attachments)
{
if (attachment.IsAttachment && attachment is MimePart attachmentPart)
2025-02-16 11:35:43 +01:00
{
2025-02-16 11:54:23 +01:00
renderingModel.Attachments.Add(attachmentPart);
}
2025-02-16 11:54:23 +01:00
}
2025-02-16 11:35:43 +01:00
2025-02-16 11:54:23 +01:00
if (message.Headers.Contains(HeaderId.ListUnsubscribe))
{
var unsubscribeLinks = message.Headers[HeaderId.ListUnsubscribe]
.Normalize()
.Split([','], StringSplitOptions.RemoveEmptyEntries)
.Select(x => x.Trim().Trim(['<', '>']));
2025-02-16 11:54:23 +01:00
// Only two types of unsubscribe links are possible.
// So each has it's own property to simplify the usage.
renderingModel.UnsubscribeInfo = new UnsubscribeInfo()
{
HttpLink = unsubscribeLinks.FirstOrDefault(x => x.StartsWith("http", StringComparison.OrdinalIgnoreCase)),
MailToLink = unsubscribeLinks.FirstOrDefault(x => x.StartsWith("mailto", StringComparison.OrdinalIgnoreCase)),
IsOneClick = message.Headers.Contains(HeaderId.ListUnsubscribePost)
};
}
2025-02-16 11:54:23 +01:00
return renderingModel;
2024-04-18 01:44:37 +02:00
}
public async Task DeleteUserMimeCacheAsync(Guid accountId)
{
var mimeFolderPath = await _nativeAppService.GetMimeMessageStoragePath().ConfigureAwait(false);
var mimeDirectory = Path.Combine(mimeFolderPath, accountId.ToString());
try
{
if (Directory.Exists(mimeDirectory))
{
Directory.Delete(mimeDirectory, true);
}
}
catch (Exception ex)
{
Log.Error(ex, "Failed to remove user's mime cache folder.");
}
}
2024-04-18 01:44:37 +02:00
}