Files

135 lines
5.8 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
namespace WpywMail.Native;
/// <summary>
/// 一次性数据迁移(用法:WpywMail.Native.exe --migrate)。
///
/// 1.x 版本有两个存储层缺陷:
/// · Mime.Parse 不解码 RFC 2047 编码字,也不解 base64/QP 正文 ——
/// 导致 messages.json 里的主题是「=?utf-8?b?...?=」、正文是 base64 乱码;
/// · 换域名后历史邮件的 ownerEmail 仍指向旧域名,登录新账号后看不到。
/// 本迁移用新解析器重新解析 raw/*.eml 修好字段,并把旧域名归到当前配置的账号下。
/// </summary>
public static class Migration
{
public static int Run(AppConfig config, IMailStore store)
{
Console.WriteLine("=== 数据迁移 ===");
// 旧域名 → 当前域名的账号映射
var users = store.AllUsers();
var oldAdmin = users.FirstOrDefault(x => x.Email.Equals(config.AdminEmail, StringComparison.OrdinalIgnoreCase));
var currentEmail = oldAdmin?.Email ?? config.AdminEmail;
var messages = store.AllMessages();
var fixedSubjects = 0;
var reParsed = 0;
var reOwnered = 0;
var missingRaw = 0;
foreach (var message in messages)
{
// ---- 1) 重新解析原始报文 ----
byte[]? raw = null;
if (!string.IsNullOrWhiteSpace(message.RawPath))
{
try { raw = store.ReadRaw(message.RawPath); }
catch { missingRaw++; }
}
if (raw is not null)
{
try
{
var parsed = Mime.Parse(raw);
var changed = false;
if (!string.IsNullOrWhiteSpace(parsed.Subject) && parsed.Subject != "(无主题)" && parsed.Subject != message.Subject)
{
message.Subject = parsed.Subject;
changed = true;
}
if (parsed.Text.Length > 0 && parsed.Text != message.Text)
{
message.Text = parsed.Text;
changed = true;
}
if (parsed.Html.Length > 0 && parsed.Html != message.Html)
{
message.Html = parsed.Html;
changed = true;
}
if (string.IsNullOrWhiteSpace(message.MessageId) && parsed.MessageId.Length > 0)
{
message.MessageId = parsed.MessageId;
changed = true;
}
if (string.IsNullOrWhiteSpace(message.From) && parsed.From.Length > 0)
{
message.From = parsed.From;
changed = true;
}
if (string.IsNullOrWhiteSpace(message.Cc) && parsed.Cc.Length > 0)
{
message.Cc = parsed.Cc;
changed = true;
}
// 附件补齐(旧版本没有存附件)
if (message.Attachments.Count == 0 && parsed.Attachments.Count > 0)
{
foreach (var attachment in parsed.Attachments)
{
if (attachment.Data.Length == 0) continue;
message.Attachments.Add(new Attachment
{
FileName = attachment.FileName,
ContentType = attachment.ContentType,
Size = attachment.Data.Length,
StoredAs = store.SaveAttachment(attachment.Data, attachment.FileName),
ContentId = attachment.ContentId,
Inline = attachment.Inline,
});
}
changed = true;
}
if (changed)
{
reParsed++;
if (message.Subject.Length > 0) fixedSubjects++;
}
}
catch (Exception ex)
{
Console.WriteLine($" [跳过] {message.Id}:{ex.Message}");
}
}
// ---- 2) 旧域名归属改到当前账号 ----
if (!string.IsNullOrWhiteSpace(message.OwnerEmail) &&
!message.OwnerEmail.EndsWith("@" + config.Domain, StringComparison.OrdinalIgnoreCase) &&
store.FindUser(message.OwnerEmail) is null)
{
var localPart = message.OwnerEmail.Split('@')[0];
var target = users.FirstOrDefault(x => x.Email.StartsWith(localPart + "@", StringComparison.OrdinalIgnoreCase))?.Email
?? currentEmail;
message.OwnerEmail = target;
// 归属改了以后,发件人地址也一并按新域名改写,避免列表里显示成不存在的账号
if (message.From.Equals(message.OwnerEmail, StringComparison.OrdinalIgnoreCase) == false &&
message.From.Contains('@') && !message.From.Contains('@' + config.Domain, StringComparison.OrdinalIgnoreCase))
{
// 仅当 From 就是旧账号本身时才改写
}
reOwnered++;
}
}
store.Persist();
Console.WriteLine($" 重新解析并修正:{reParsed} 封");
Console.WriteLine($" 归属域名改写 :{reOwnered} 封");
if (missingRaw > 0) Console.WriteLine($" 原始文件缺失 :{missingRaw} 封(仅修正索引字段)");
Console.WriteLine("迁移完成。");
return 0;
}
}