Code
·
74 lines
·
3028 bytes
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74using 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<ContactAddress>? GetCachedContacts(int accountId);
void SaveContacts(int accountId, List<ContactAddress> 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<ContactAddress> 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<ContactOverride> Overrides => _db.GetCollection<ContactOverride>("contactOverrides");
private ILiteCollection<ContactCache> Caches => _db.GetCollection<ContactCache>("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<ContactAddress>? GetCachedContacts(int accountId) =>
Caches.FindById(accountId)?.Contacts;
public void SaveContacts(int accountId, List<ContactAddress> contacts) =>
Caches.Upsert(new ContactCache { Id = accountId, Contacts = contacts });
public void AddContacts(int accountId, IEnumerable<ContactAddress> 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();
}