simplify IDLE mechanism and _send() logic

fork
mscdex 12 years ago
parent 95902ec770
commit a99b2c0084

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

Loading…
Cancel
Save