Projektstart
This commit is contained in:
211
backend/node_modules/imapflow/lib/commands/idle.js
generated
vendored
Normal file
211
backend/node_modules/imapflow/lib/commands/idle.js
generated
vendored
Normal file
@@ -0,0 +1,211 @@
|
||||
'use strict';
|
||||
|
||||
const NOOP_INTERVAL = 2 * 60 * 1000;
|
||||
|
||||
async function runIdle(connection) {
|
||||
let response;
|
||||
|
||||
let preCheckWaitQueue = [];
|
||||
try {
|
||||
connection.idling = true;
|
||||
|
||||
//let idleSent = false;
|
||||
let doneRequested = false;
|
||||
let doneSent = false;
|
||||
let canEnd = false;
|
||||
|
||||
let preCheck = async () => {
|
||||
doneRequested = true;
|
||||
if (canEnd && !doneSent) {
|
||||
connection.log.debug({
|
||||
src: 'c',
|
||||
msg: `DONE`,
|
||||
comment: `breaking IDLE`,
|
||||
lockId: connection.currentLock?.lockId,
|
||||
path: connection.mailbox && connection.mailbox.path
|
||||
});
|
||||
connection.write('DONE');
|
||||
doneSent = true;
|
||||
|
||||
connection.idling = false;
|
||||
connection.preCheck = false; // unset itself
|
||||
|
||||
while (preCheckWaitQueue.length) {
|
||||
let { resolve } = preCheckWaitQueue.shift();
|
||||
resolve();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let connectionPreCheck = () => {
|
||||
let handler = new Promise((resolve, reject) => {
|
||||
preCheckWaitQueue.push({ resolve, reject });
|
||||
});
|
||||
|
||||
connection.log.trace({
|
||||
msg: 'Requesting IDLE break',
|
||||
lockId: connection.currentLock?.lockId,
|
||||
path: connection.mailbox && connection.mailbox.path,
|
||||
queued: preCheckWaitQueue.length,
|
||||
doneRequested,
|
||||
canEnd,
|
||||
doneSent
|
||||
});
|
||||
|
||||
preCheck().catch(err => connection.log.warn({ err, cid: connection.id }));
|
||||
|
||||
return handler;
|
||||
};
|
||||
|
||||
connection.preCheck = connectionPreCheck;
|
||||
|
||||
response = await connection.exec('IDLE', false, {
|
||||
onPlusTag: async () => {
|
||||
connection.log.debug({ msg: `Initiated IDLE, waiting for server input`, lockId: connection.currentLock?.lockId, doneRequested });
|
||||
canEnd = true;
|
||||
if (doneRequested) {
|
||||
try {
|
||||
await preCheck();
|
||||
} catch (err) {
|
||||
connection.log.warn({ err, cid: connection.id });
|
||||
}
|
||||
}
|
||||
},
|
||||
onSend: () => {
|
||||
//idleSent = true;
|
||||
}
|
||||
});
|
||||
|
||||
// unset before response.next() if preCheck function is not already cleared (usually is)
|
||||
if (typeof connection.preCheck === 'function' && connection.preCheck === connectionPreCheck) {
|
||||
connection.log.trace({
|
||||
msg: 'Clearing pre-check function',
|
||||
lockId: connection.currentLock?.lockId,
|
||||
path: connection.mailbox && connection.mailbox.path,
|
||||
queued: preCheckWaitQueue.length,
|
||||
doneRequested,
|
||||
canEnd,
|
||||
doneSent
|
||||
});
|
||||
connection.preCheck = false;
|
||||
while (preCheckWaitQueue.length) {
|
||||
let { resolve } = preCheckWaitQueue.shift();
|
||||
resolve();
|
||||
}
|
||||
}
|
||||
|
||||
response.next();
|
||||
return;
|
||||
} catch (err) {
|
||||
connection.preCheck = false;
|
||||
connection.idling = false;
|
||||
|
||||
connection.log.warn({ err, cid: connection.id });
|
||||
while (preCheckWaitQueue.length) {
|
||||
let { reject } = preCheckWaitQueue.shift();
|
||||
reject(err);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Listens for changes in mailbox
|
||||
module.exports = async (connection, maxIdleTime) => {
|
||||
if (connection.state !== connection.states.SELECTED) {
|
||||
// nothing to do here
|
||||
return;
|
||||
}
|
||||
|
||||
if (connection.capabilities.has('IDLE')) {
|
||||
let idleTimer;
|
||||
let stillIdling = false;
|
||||
let runIdleLoop = async () => {
|
||||
if (maxIdleTime) {
|
||||
idleTimer = setTimeout(() => {
|
||||
if (connection.idling) {
|
||||
if (typeof connection.preCheck === 'function') {
|
||||
stillIdling = true;
|
||||
// request IDLE break if IDLE has been running for allowed time
|
||||
connection.log.trace({ msg: 'Max allowed IDLE time reached', cid: connection.id });
|
||||
connection.preCheck().catch(err => connection.log.warn({ err, cid: connection.id }));
|
||||
}
|
||||
}
|
||||
}, maxIdleTime);
|
||||
}
|
||||
let resp = await runIdle(connection);
|
||||
clearTimeout(idleTimer);
|
||||
if (stillIdling) {
|
||||
stillIdling = false;
|
||||
return runIdleLoop();
|
||||
}
|
||||
return resp;
|
||||
};
|
||||
return runIdleLoop();
|
||||
}
|
||||
|
||||
let idleTimer;
|
||||
return new Promise(resolve => {
|
||||
if (!connection.currentSelectCommand) {
|
||||
return resolve();
|
||||
}
|
||||
|
||||
// no IDLE support, fallback to NOOP'ing
|
||||
connection.preCheck = async () => {
|
||||
connection.preCheck = false; // unset itself
|
||||
clearTimeout(idleTimer);
|
||||
connection.log.debug({ src: 'c', msg: `breaking NOOP loop` });
|
||||
connection.idling = false;
|
||||
resolve();
|
||||
};
|
||||
|
||||
let selectCommand = connection.currentSelectCommand;
|
||||
|
||||
let idleCheck = async () => {
|
||||
let response;
|
||||
switch (connection.missingIdleCommand) {
|
||||
case 'SELECT':
|
||||
// FIXME: somehow a loop occurs after some time of idling with SELECT
|
||||
connection.log.debug({ src: 'c', msg: `Running SELECT to detect changes in folder` });
|
||||
response = await connection.exec(selectCommand.command, selectCommand.arguments);
|
||||
break;
|
||||
|
||||
case 'STATUS':
|
||||
{
|
||||
let statusArgs = [selectCommand.arguments[0], []]; // path
|
||||
for (let key of ['MESSAGES', 'UIDNEXT', 'UIDVALIDITY', 'UNSEEN']) {
|
||||
statusArgs[1].push({ type: 'ATOM', value: key.toUpperCase() });
|
||||
}
|
||||
connection.log.debug({ src: 'c', msg: `Running STATUS to detect changes in folder` });
|
||||
response = await connection.exec('STATUS', statusArgs);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'NOOP':
|
||||
default:
|
||||
response = await connection.exec('NOOP', false, { comment: 'IDLE not supported' });
|
||||
break;
|
||||
}
|
||||
response.next();
|
||||
};
|
||||
|
||||
let noopInterval = maxIdleTime ? Math.min(NOOP_INTERVAL, maxIdleTime) : NOOP_INTERVAL;
|
||||
|
||||
let runLoop = () => {
|
||||
idleCheck()
|
||||
.then(() => {
|
||||
clearTimeout(idleTimer);
|
||||
idleTimer = setTimeout(runLoop, noopInterval);
|
||||
})
|
||||
.catch(err => {
|
||||
clearTimeout(idleTimer);
|
||||
connection.preCheck = false;
|
||||
connection.log.warn({ err, cid: connection.id });
|
||||
resolve();
|
||||
});
|
||||
};
|
||||
|
||||
connection.log.debug({ src: 'c', msg: `initiated NOOP loop` });
|
||||
connection.idling = true;
|
||||
runLoop();
|
||||
});
|
||||
};
|
||||
Reference in New Issue
Block a user