simplify IDLE mechanism and _send() logic

fork
mscdex 11 years ago
parent 95902ec770
commit a99b2c0084

@ -26,6 +26,8 @@ var CRLF = '\r\n',
RE_UNRESP = /^\* (OK|PREAUTH|NO|BAD)(?:\r\n| (?:\[(.+)\] )?(.+))$/i,
RE_TAGGED_RESP = /^A\d+ (OK|NO|BAD) (?:\[(.+?)\] )?(.+)$/i,
RE_TEXT_CODE = /([^ ]+)(?: (.*))?$/,
RE_RES_IDLE = /^IDLE /i,
RE_RES_NOOP = /^NOOP /i,
//RE_ISPARTIAL = /<(\d+)>$/,
RE_DBLQ = /"/g,
RE_CMD = /^([^ ]+)(?: |$)/,
@ -36,8 +38,7 @@ var CRLF = '\r\n',
// extension constants
var IDLE_NONE = 1,
IDLE_WAIT = 2,
IDLE_READY = 3,
IDLE_DONE = 4;
IDLE_IDLING = 3;
function ImapConnection(options) {
if (!(this instanceof ImapConnection))
@ -93,7 +94,6 @@ function ImapConnection(options) {
idle: {
MAX_WAIT: 300000, // 5 mins in ms
state: IDLE_NONE,
reIDLE: false,
timeStarted: undefined
}
}
@ -309,7 +309,7 @@ ImapConnection.prototype.connect = function(loginCb) {
if (indata.line[0] === '*') { // Untagged server response
var isUnsolicited =
(requests[0] && requests[0].cmd === 'NOOP')
|| (state.isIdle && state.ext.idle.state === IDLE_READY);
|| (state.isIdle && state.ext.idle.state === IDLE_IDLING);
if (m = XRegExp.exec(indata.line, REX_UNRESPNUM)) {
// m.type = response type (numeric-based)
m.type = m.type.toUpperCase();
@ -624,18 +624,21 @@ ImapConnection.prototype.connect = function(loginCb) {
indata.temp = undefined;
indata.streaming = false;
indata.expect = -1;
self.debug&&self.debug(line[0] === 'A'
? '[parsing incoming] saw tagged response'
: '[parsing incoming] saw continuation response');
clearTimeout(state.tmrKeepalive);
if (line[0] === '+' && state.ext.idle.state === IDLE_WAIT) {
state.ext.idle.state = IDLE_READY;
state.ext.idle.state = IDLE_IDLING;
state.ext.idle.timeStarted = Date.now();
doKeepaliveTimer();
return process.nextTick(function() { self._send(); });
}
var sendBox = false;
clearTimeout(state.tmrKeepalive);
if (state.status === STATES.BOXSELECTING) {
if (/^A\d+ OK/i.test(line)) {
sendBox = true;
@ -646,7 +649,6 @@ ImapConnection.prototype.connect = function(loginCb) {
self._resetBox();
}
}
if (requests[0].cmd === 'RENAME') {
if (state.box._newName) {
state.box.name = state.box._newName;
@ -711,41 +713,33 @@ ImapConnection.prototype.connect = function(loginCb) {
requests[0].callback.apply(self, args);
}
var recentCmd = requests[0].cmdstr;
var recentCmd = requests[0].cmd;
requests.shift();
if (requests.length === 0 && recentCmd !== 'LOGOUT') {
if (state.status >= STATES.AUTH && self.serverSupports('IDLE')) {
// According to RFC 2177, we should re-IDLE at least every 29
// minutes to avoid disconnection by the server
self._send('IDLE', undefined, true);
}
state.tmrKeepalive = setTimeout(function idleHandler() {
if (state.isIdle) {
if (state.ext.idle.state === IDLE_READY) {
state.tmrKeepalive = setTimeout(idleHandler, state.tmoKeepalive);
var timeDiff = Date.now() - state.ext.idle.timeStarted;
if (timeDiff >= state.ext.idle.MAX_WAIT)
self._send('IDLE', undefined, true); // restart IDLE
} else if (!self.serverSupports('IDLE'))
self._noop();
}
}, state.tmoKeepalive);
} else
if (!requests.length && recentCmd !== 'LOGOUT')
doKeepalive();
else
process.nextTick(function() { self._send(); });
state.isIdle = true;
} else if (/^IDLE /i.test(indata.line)) {
} else if (RE_RES_IDLE.test(indata.line)) {
self.debug&&self.debug('[parsing incoming] saw IDLE');
if (requests.length)
process.nextTick(function() { self._send(); });
state.isIdle = false;
state.ext.idle.state = IDLE_NONE;
state.ext.idle.timeStated = undefined;
requests.shift(); // remove IDLE request
indata.line = undefined;
if (state.ext.idle.reIDLE) {
state.ext.idle.reIDLE = false;
self._send('IDLE', undefined, true);
}
state.ext.idle.state = IDLE_NONE;
state.ext.idle.timeStarted = undefined;
if (requests.length) {
state.isIdle = false;
self._send();
} else
doKeepalive();
} else if (RE_RES_NOOP.test(indata.line)) {
self.debug&&self.debug('[parsing incoming] saw NOOP');
requests.shift(); // remove NOOP request
if (!requests.length)
doKeepaliveTimer();
else
self._send();
} else {
// unknown response
self.debug&&self.debug('[parsing incoming] saw unexpected response: '
@ -754,6 +748,30 @@ ImapConnection.prototype.connect = function(loginCb) {
}
}
function doKeepalive() {
if (state.status >= STATES.AUTH) {
if (self.serverSupports('IDLE'))
self._send('IDLE');
else
self._noop();
}
}
function doKeepaliveTimer() {
state.tmrKeepalive = setTimeout(function idleHandler() {
if (state.isIdle) {
if (state.ext.idle.state === IDLE_IDLING) {
var timeDiff = Date.now() - state.ext.idle.timeStarted;
if (timeDiff >= state.ext.idle.MAX_WAIT)
self._send('DONE');
else
state.tmrKeepalive = setTimeout(idleHandler, state.tmoKeepalive);
} else if (!self.serverSupports('IDLE'))
doKeepalive();
}
}, state.tmoKeepalive);
}
state.conn.connect(this._options.port, this._options.host);
state.tmrConn = setTimeout(function() {
@ -1503,7 +1521,6 @@ ImapConnection.prototype._reset = function() {
this._state.tmrConn = null;
this._state.ext.idle.state = IDLE_NONE;
this._state.ext.idle.timeStarted = undefined;
this._state.ext.idle.reIDLE = false;
this._state.indata.literals = [];
this._state.indata.line = undefined;
@ -1539,47 +1556,56 @@ ImapConnection.prototype._noop = function() {
this._send('NOOP');
};
ImapConnection.prototype._send = function(cmdstr, cb, bypass) {
ImapConnection.prototype._send = function(cmdstr, cb) {
if (!this._state.conn.writable)
return;
if (cmdstr !== undefined && !bypass) {
this._state.requests.push({
var reqs = this._state.requests, idle = this._state.ext.idle;
if (cmdstr !== undefined) {
var info = {
cmd: cmdstr.match(RE_CMD)[1],
cmdstr: cmdstr,
callback: cb,
cbargs: []
});
};
if (cmdstr === 'IDLE' || cmdstr === 'DONE' || cmdstr === 'NOOP')
reqs.unshift(info);
else
reqs.push(info);
}
if (this._state.ext.idle.state === IDLE_WAIT
|| (this._state.ext.idle.state === IDLE_DONE && cmdstr !== 'DONE'))
if (idle.state !== IDLE_NONE && cmdstr !== 'DONE') {
if (cmdstr !== undefined)
this._send('DONE');
return;
if ((cmdstr === undefined && this._state.requests.length)
|| this._state.requests.length === 1 || bypass) {
var prefix = '',
cmd = (bypass ? cmdstr : this._state.requests[0].cmdstr);
}
if ((cmdstr === undefined && reqs.length) || reqs.length === 1
|| cmdstr === 'DONE') {
var prefix = '', curReq = reqs[0];
cmdstr = curReq.cmdstr;
clearTimeout(this._state.tmrKeepalive);
if (this._state.ext.idle.state === IDLE_READY && cmd !== 'DONE') {
this._state.ext.idle.state = IDLE_DONE;
if (cmd === 'IDLE')
this._state.ext.idle.reIDLE = true;
return this._send('DONE', undefined, true);
} else if (cmd === 'IDLE') {
// we use a different prefix to differentiate and disregard the tagged
// response the server will send us when we issue DONE
if (cmdstr === 'IDLE') {
// we use a different prefix to differentiate and disregard the tagged
// response the server will send us when we issue DONE
prefix = 'IDLE ';
this._state.ext.idle.state = IDLE_WAIT;
}
if (cmd !== 'IDLE' && cmd !== 'DONE')
} else if (cmdstr === 'NOOP')
prefix = 'NOOP ';
else if (cmdstr !== 'DONE')
prefix = 'A' + (++this._state.curId) + ' ';
this._state.conn.write(prefix);
this._state.conn.write(cmd);
this._state.conn.write(CRLF);
this.debug&&this.debug('\n==> ' + prefix + cmd + '\n');
if (this._state.requests[0]
&& (this._state.requests[0].cmd === 'EXAMINE'
|| this._state.requests[0].cmd === 'SELECT'))
this._state.conn.write(prefix + cmdstr + CRLF);
this.debug&&this.debug('\n==> ' + prefix + cmdstr + '\n');
if (curReq.cmd === 'EXAMINE' || curReq.cmd === 'SELECT')
this._state.status = STATES.BOXSELECTING;
else if (cmdstr === 'DONE')
reqs.shift();
}
};

Loading…
Cancel
Save