Mail: faster open/delete (instant delete and archive, separate background IMAP connection, one-fetch message read, bodies saved on device with prefetch); Lyrics: no sweep across line-synced lines
build-container / build (push) Successful in 10m28s
build-container / build (push) Successful in 10m28s
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
3f57e4bde1
commit
bbbb8ef5d5
4 files changed
+62
-28
No files matched your search
+17
-18
@@ -41,27 +41,28 @@ function friendly(a, e) {
|
||||
// ---- connections: one per account, kept for a minute after the last use
|
||||
const IDLE = 5 * 60e3; // a connection is kept this long after its last use: opening one (TLS and login) is the slowest part of a request
|
||||
const pool = new Map(); // account id -> { client, timer, key }
|
||||
const forget = id => { folderCache.delete(id); boxCache.delete(id); const e = pool.get(id); if (e) { clearTimeout(e.timer); try { e.client.close(); } catch {} pool.delete(id); } };
|
||||
const drop = id => { const e = pool.get(id); if (e) { clearTimeout(e.timer); try { e.client.close(); } catch {} pool.delete(id); } };
|
||||
const forget = id => { folderCache.delete(id); boxCache.delete(id); drop(id); drop(id + ':bg'); };
|
||||
const keyOf = a => a.address + '\n' + a.pass;
|
||||
async function open(a) {
|
||||
const e = pool.get(a.id);
|
||||
if (e && e.key === keyOf(a) && e.client.usable) { clearTimeout(e.timer); e.timer = setTimeout(() => forget(a.id), IDLE).unref(); return e.client; }
|
||||
forget(a.id);
|
||||
async function open(a, slot) { // slot 'bg': a second connection for counts and folder lists, so they never queue in front of opening or deleting a message
|
||||
const pid = slot ? a.id + ':' + slot : a.id, e = pool.get(pid);
|
||||
if (e && e.key === keyOf(a) && e.client.usable) { clearTimeout(e.timer); e.timer = setTimeout(() => drop(pid), IDLE).unref(); return e.client; }
|
||||
drop(pid);
|
||||
const c = cfgOf(a);
|
||||
const client = new ImapFlow({ host: c.host, port: c.port, secure: c.secure, ...(c.test ? { doSTARTTLS: false, tls: { rejectUnauthorized: false } } : {}), auth: { user: a.address, pass: a.pass }, logger: false, emitLogs: false, socketTimeout: 60e3, greetingTimeout: 20e3, connectionTimeout: 20e3 });
|
||||
client.on('error', () => {}); client.on('close', () => { const cur = pool.get(a.id); if (cur && cur.client === client) pool.delete(a.id); });
|
||||
client.on('error', () => {}); client.on('close', () => { const cur = pool.get(pid); if (cur && cur.client === client) pool.delete(a.id); });
|
||||
try { await client.connect(); } catch (err) { throw fail(err.authenticationFailed ? 401 : 502, friendly(a, err)); }
|
||||
pool.set(a.id, { client, key: keyOf(a), timer: setTimeout(() => forget(a.id), IDLE).unref() });
|
||||
pool.set(pid, { client, key: keyOf(a), timer: setTimeout(() => drop(pid), IDLE).unref() });
|
||||
return client;
|
||||
}
|
||||
/** Run `fn(client)`; if the connection had gone stale, reconnect once. */
|
||||
async function withClient(a, fn) {
|
||||
async function withClient(a, fn, slot) {
|
||||
for (let attempt = 0; ; attempt++) {
|
||||
const client = await open(a);
|
||||
const client = await open(a, slot);
|
||||
try { return await fn(client); }
|
||||
catch (err) {
|
||||
if (err.status) throw err;
|
||||
if (attempt === 0 && (!client.usable || /closed|ECONNRESET|socket|EPIPE|Connection not available/i.test(err.message || ''))) { forget(a.id); continue; }
|
||||
if (attempt === 0 && (!client.usable || /closed|ECONNRESET|socket|EPIPE|Connection not available/i.test(err.message || ''))) { drop(slot ? a.id + ':' + slot : a.id); continue; }
|
||||
throw fail(502, friendly(a, err));
|
||||
}
|
||||
}
|
||||
@@ -105,7 +106,7 @@ async function folders(a) {
|
||||
const rows = keep.map(b => { const k = byPath[b.path], st = counts.get(b.path) || {}; return { id: k || 'p-' + Buffer.from(b.path).toString('base64url'), name: k ? NAMES[k] : b.path.replace(/^\[.*?\]\//, ''), unread: st.unseen || 0, total: st.messages || 0, well: !!k }; });
|
||||
const order = Object.keys(NAMES);
|
||||
return rows.sort((x, y) => (x.well ? order.indexOf(x.id) : 99) - (y.well ? order.indexOf(y.id) : 99) || x.name.localeCompare(y.name));
|
||||
});
|
||||
}, 'bg');
|
||||
folderCache.set(a.id, { t: Date.now(), v }); return v;
|
||||
}
|
||||
|
||||
@@ -141,12 +142,10 @@ async function source(a, mid) {
|
||||
return withClient(a, async client => {
|
||||
const lock = await client.getMailboxLock(path);
|
||||
try {
|
||||
const dl = await client.download(String(uid), undefined, { uid: true });
|
||||
if (!dl || !dl.content) throw fail(404, 'That message is gone');
|
||||
const chunks = []; let n = 0;
|
||||
for await (const ch of dl.content) { n += ch.length; if (n > MAX_SOURCE) throw fail(400, 'That message is too large to open here'); chunks.push(ch); }
|
||||
const meta = await client.fetchOne(String(uid), { flags: true }, { uid: true }).catch(() => null);
|
||||
return { parsed: await simpleParser(Buffer.concat(chunks)), path, uid, flags: meta ? meta.flags : new Set() };
|
||||
const m = await client.fetchOne(String(uid), { source: { maxLength: MAX_SOURCE + 1 }, flags: true }, { uid: true }); // (one round trip for the body and the flags)
|
||||
if (!m || !m.source) throw fail(404, 'That message is gone');
|
||||
if (m.source.length > MAX_SOURCE) throw fail(400, 'That message is too large to open here');
|
||||
return { parsed: await simpleParser(m.source), path, uid, flags: m.flags || new Set() };
|
||||
} finally { lock.release(); }
|
||||
});
|
||||
}
|
||||
@@ -193,7 +192,7 @@ async function del(a, mid, permanent) {
|
||||
}
|
||||
/** Counts shown in the folder list are kept 30 s; reading, moving, deleting or sending changes them, so the next request must ask again. */
|
||||
const dropFolders = id => { if (id) folderCache.delete(id); else folderCache.clear(); };
|
||||
async function unread(a) { return withClient(a, async client => (await client.status('INBOX', { unseen: true })).unseen || 0); }
|
||||
async function unread(a) { return withClient(a, async client => (await client.status('INBOX', { unseen: true })).unseen || 0, 'bg'); }
|
||||
|
||||
// ---- sending
|
||||
function transport(a) {
|
||||
|
||||
Reference in new issue
Block a user