using LiteDB; using MailSharp.MailClient.Models; namespace MailSharp.MailClient.Services; public interface IContactStore { string? GetDisplayName(int accountId, string email); void SetDisplayName(int accountId, string email, string displayName); // Cached contact address list, keyed by account. Null means "never populated yet" - the // caller should fetch from IMAP once and call SaveContacts. List? GetCachedContacts(int accountId); void SaveContacts(int accountId, List contacts); // Merges newly-seen recipients (from a send/reply/forward) into the existing cache without // re-fetching the whole Sent folder. No-op if the cache hasn't been populated yet - the next // full list request will pick these up anyway. void AddContacts(int accountId, IEnumerable contacts); } // Stores the user's own edits to a contact's display name, plus the cached contact address list // itself, keyed by account. The address list is expensive to build (a full walk of the Sent // folder over IMAP - see IImapService.GetSentContactsAsync), so it's fetched once and then kept // up to date incrementally as mail is sent, rather than being re-derived on every page load. public class LiteDbContactStore : IContactStore, IDisposable { private readonly LiteDatabase _db; public LiteDbContactStore(IWebHostEnvironment env) { var appDataDir = Path.Combine(env.ContentRootPath, "App_Data"); Directory.CreateDirectory(appDataDir); _db = new LiteDatabase(Path.Combine(appDataDir, "contacts.db")); } private ILiteCollection Overrides => _db.GetCollection("contactOverrides"); private ILiteCollection Caches => _db.GetCollection("contactCaches"); private static string Key(int accountId, string email) => $"{accountId}:{email.Trim().ToLowerInvariant()}"; public string? GetDisplayName(int accountId, string email) => Overrides.FindById(Key(accountId, email))?.DisplayName; public void SetDisplayName(int accountId, string email, string displayName) => Overrides.Upsert(new ContactOverride { Id = Key(accountId, email), DisplayName = displayName }); public List? GetCachedContacts(int accountId) => Caches.FindById(accountId)?.Contacts; public void SaveContacts(int accountId, List contacts) => Caches.Upsert(new ContactCache { Id = accountId, Contacts = contacts }); public void AddContacts(int accountId, IEnumerable contacts) { var cache = Caches.FindById(accountId); if (cache == null) return; var byEmail = cache.Contacts.ToDictionary(c => c.Email, StringComparer.OrdinalIgnoreCase); var changed = false; foreach (var c in contacts) { if (string.IsNullOrWhiteSpace(c.Email) || byEmail.ContainsKey(c.Email)) continue; byEmail[c.Email] = c; changed = true; } if (!changed) return; cache.Contacts = [.. byEmail.Values.OrderBy(c => c.Email)]; Caches.Update(cache); } public void Dispose() => _db.Dispose(); }