Much faster resync with uidlist
parent
40fd67114a
commit
a0bb2f2fae
222
operetta.js
222
operetta.js
|
@ -5,10 +5,6 @@ require('heapdump');
|
||||||
var gen = require('gen-thread');
|
var gen = require('gen-thread');
|
||||||
var Imap = require('imap');
|
var Imap = require('imap');
|
||||||
var inspect = require('util').inspect;
|
var inspect = require('util').inspect;
|
||||||
//var pg;
|
|
||||||
//try { require('pg-native'); pg = require('pg').native; }
|
|
||||||
//catch(e) { pg = require('pg'); }
|
|
||||||
//var pg_pool = new pg.Pool(cfg.pg);
|
|
||||||
|
|
||||||
var bricks = require('pg-bricks');
|
var bricks = require('pg-bricks');
|
||||||
var pg = bricks.configure('postgresql://'+cfg.pg.user+':'+cfg.pg.password+'@'+(cfg.pg.host||'')+':'+cfg.pg.port+'/'+cfg.pg.database);
|
var pg = bricks.configure('postgresql://'+cfg.pg.user+':'+cfg.pg.password+'@'+(cfg.pg.host||'')+':'+cfg.pg.port+'/'+cfg.pg.database);
|
||||||
|
@ -73,7 +69,7 @@ Syncer.sync = function*(account)
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
yield pg.update('messages', { flags: pg.sql('(flags || array[\'deleted\']::varchar(255)[])') }).where({ folder_id: row.id })
|
yield pg.update('messages', { flags: pg.sql('(array_remove(flags, \'deleted\') || array[\'deleted\']::varchar(255)[])') }).where({ folder_id: row.id })
|
||||||
.where(pg.sql('uid is not null')).run(gen.ef());
|
.where(pg.sql('uid is not null')).run(gen.ef());
|
||||||
}
|
}
|
||||||
yield pg.update('folders', { uidvalidity: box.uidvalidity, unread_count: box.messages.new })
|
yield pg.update('folders', { uidvalidity: box.uidvalidity, unread_count: box.messages.new })
|
||||||
|
@ -90,54 +86,13 @@ Syncer.sync = function*(account)
|
||||||
}).returning('id').row(gen.ef());
|
}).returning('id').row(gen.ef());
|
||||||
boxId = row.id;
|
boxId = row.id;
|
||||||
}
|
}
|
||||||
var f = srv.fetch('1:*', {
|
|
||||||
size: true,
|
|
||||||
bodies: 'HEADER'
|
|
||||||
});
|
|
||||||
|
|
||||||
self.parsed = 0;
|
// list messages, update flags
|
||||||
self.paused = false;
|
process.stderr.write('\rsynchronizing 0');
|
||||||
self.synced = 0;
|
yield* self.runFetch('1:*', {}, boxId, 'updateFlags');
|
||||||
self.pending = [];
|
process.stderr.write('\n');
|
||||||
|
|
||||||
f.on('message', function(msg, seqnum)
|
// delete messages removed from IMAP server
|
||||||
{
|
|
||||||
gen.run(function*()
|
|
||||||
{
|
|
||||||
var [ msgrow, attrs ] = yield* self.parseMessage(msg, seqnum, boxId);
|
|
||||||
|
|
||||||
// Workaround memory leak in node-imap
|
|
||||||
// TODO: send pull request
|
|
||||||
if (srv._curReq.fetchCache)
|
|
||||||
delete srv._curReq.fetchCache[seqnum];
|
|
||||||
|
|
||||||
self.pending.push([ msgrow, attrs ]);
|
|
||||||
self.parsed++;
|
|
||||||
if (!self.paused && self.parsed >= 20)
|
|
||||||
{
|
|
||||||
// ГОРШОЧЕК, НЕ ВАРИ!!! И так уже кучу сообщений прочитал из сокета, хорош!
|
|
||||||
srv._parser._ignoreReadable = true;
|
|
||||||
self.paused = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (self.pending.length >= 20)
|
|
||||||
{
|
|
||||||
var m = self.pending;
|
|
||||||
self.pending = [];
|
|
||||||
yield gen.run(self.saveMessages(m, boxId), gen.cb());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
var cb = gen.cb();
|
|
||||||
yield f.once('end', function()
|
|
||||||
{
|
|
||||||
process.stderr.write('\n');
|
|
||||||
if (self.pending.length > 0)
|
|
||||||
gen.run(self.saveMessages(self.pending, boxId));
|
|
||||||
self.pending = [];
|
|
||||||
cb();
|
|
||||||
});
|
|
||||||
console.log(boxId);
|
|
||||||
yield pg.update('threads', { first_msg: null })
|
yield pg.update('threads', { first_msg: null })
|
||||||
.where(pg.sql('first_msg IN ('+
|
.where(pg.sql('first_msg IN ('+
|
||||||
pg.select('id').from('messages').where({ folder_id: boxId })
|
pg.select('id').from('messages').where({ folder_id: boxId })
|
||||||
|
@ -150,12 +105,103 @@ Syncer.sync = function*(account)
|
||||||
pg.select('MIN(id)').from('messages').where({ thread_id: pg.sql('threads.id') })+')'
|
pg.select('MIN(id)').from('messages').where({ thread_id: pg.sql('threads.id') })+')'
|
||||||
) }).where(pg.sql('first_msg IS NULL')).run(gen.ef());
|
) }).where(pg.sql('first_msg IS NULL')).run(gen.ef());
|
||||||
yield pg.delete('threads').where(pg.sql('first_msg IS NULL')).run(gen.ef());
|
yield pg.delete('threads').where(pg.sql('first_msg IS NULL')).run(gen.ef());
|
||||||
|
|
||||||
|
// fetch new messages
|
||||||
|
var [ maxUid ] = yield pg.select('MAX(uid)').from('messages').where({ folder_id: boxId }).val(gen.ef());
|
||||||
|
maxUid = maxUid ? maxUid+1 : 1;
|
||||||
|
yield* self.runFetch(maxUid+':*', {
|
||||||
|
size: true,
|
||||||
|
bodies: 'HEADER'
|
||||||
|
}, boxId, 'saveMessages');
|
||||||
|
|
||||||
yield srv.closeBox(gen.cb());
|
yield srv.closeBox(gen.cb());
|
||||||
}
|
}
|
||||||
srv.end();
|
srv.end();
|
||||||
self.srv = null;
|
self.srv = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Syncer.runFetch = function*(what, params, boxId, processor)
|
||||||
|
{
|
||||||
|
var self = this;
|
||||||
|
var f = self.srv.fetch(what, params);
|
||||||
|
|
||||||
|
self.parsed = 0;
|
||||||
|
self.paused = false;
|
||||||
|
self.synced = 0;
|
||||||
|
self.pending = [];
|
||||||
|
|
||||||
|
var cb, wait;
|
||||||
|
f.on('message', function(msg, seqnum)
|
||||||
|
{
|
||||||
|
gen.run(self.onMessage(msg, seqnum, boxId, processor), checkFinish, checkFinish);
|
||||||
|
});
|
||||||
|
|
||||||
|
cb = gen.cb();
|
||||||
|
yield f.once('end', function()
|
||||||
|
{
|
||||||
|
wait = true;
|
||||||
|
if (self.parsed <= 0)
|
||||||
|
cb();
|
||||||
|
else if (self.pending.length > 0)
|
||||||
|
gen.run(self[processor](self.pending, boxId), saveLast, saveLast);
|
||||||
|
});
|
||||||
|
|
||||||
|
function saveLast()
|
||||||
|
{
|
||||||
|
self.parsed -= self.pending.length;
|
||||||
|
self.pending = [];
|
||||||
|
checkFinish();
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkFinish()
|
||||||
|
{
|
||||||
|
if (self.parsed <= 0 && wait)
|
||||||
|
{
|
||||||
|
wait = false;
|
||||||
|
cb();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Syncer.onMessage = function*(msg, seqnum, boxId, processor)
|
||||||
|
{
|
||||||
|
var self = this;
|
||||||
|
var [ msgrow, attrs ] = yield* self.parseMessage(msg, seqnum, boxId);
|
||||||
|
|
||||||
|
self.pending.push([ msgrow, attrs ]);
|
||||||
|
self.parsed++;
|
||||||
|
if (!self.paused && self.parsed >= 20)
|
||||||
|
{
|
||||||
|
// ГОРШОЧЕК, НЕ ВАРИ!!! И так уже кучу сообщений прочитал из сокета, хорош!
|
||||||
|
self.srv._parser._ignoreReadable = true;
|
||||||
|
self.paused = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (self.pending.length >= 20)
|
||||||
|
{
|
||||||
|
var m = self.pending;
|
||||||
|
self.pending = [];
|
||||||
|
var err;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
yield gen.run(self[processor](m, boxId), gen.cb());
|
||||||
|
}
|
||||||
|
catch (e)
|
||||||
|
{
|
||||||
|
err = e;
|
||||||
|
}
|
||||||
|
self.parsed -= m.length;
|
||||||
|
if (self.paused && self.parsed < 20)
|
||||||
|
{
|
||||||
|
self.paused = false;
|
||||||
|
self.srv._parser._ignoreReadable = false;
|
||||||
|
process.nextTick(self.srv._parser._cbReadable);
|
||||||
|
}
|
||||||
|
if (err)
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Syncer.parseMessage = function*(msg, seqnum, boxId)
|
Syncer.parseMessage = function*(msg, seqnum, boxId)
|
||||||
{
|
{
|
||||||
var msgrow = {};
|
var msgrow = {};
|
||||||
|
@ -182,58 +228,42 @@ Syncer.parseMessage = function*(msg, seqnum, boxId)
|
||||||
msgrow.flags = attrs.flags.map(f => f[0] == '\\' ? f.toLowerCase().replace(/^\\/, '') : f.replace(/^\$*/, '$'));
|
msgrow.flags = attrs.flags.map(f => f[0] == '\\' ? f.toLowerCase().replace(/^\\/, '') : f.replace(/^\$*/, '$'));
|
||||||
var nf = msgrow.flags.filter(f => f != 'seen');
|
var nf = msgrow.flags.filter(f => f != 'seen');
|
||||||
msgrow.flags = nf.length == msgrow.flags.length ? nf.concat(['unread']) : nf;
|
msgrow.flags = nf.length == msgrow.flags.length ? nf.concat(['unread']) : nf;
|
||||||
|
// Workaround memory leak in node-imap
|
||||||
|
// TODO: send pull request
|
||||||
|
if (this.srv._curReq.fetchCache)
|
||||||
|
delete this.srv._curReq.fetchCache[seqnum];
|
||||||
return [ msgrow, attrs ];
|
return [ msgrow, attrs ];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Syncer.updateFlags = function*(messages, boxId)
|
||||||
|
{
|
||||||
|
yield gen.throttle(3);
|
||||||
|
var self = this;
|
||||||
|
var rows = messages.map(m => ({
|
||||||
|
uid: m[0].uid,
|
||||||
|
flags: toPgArray(m[0].flags)
|
||||||
|
}));
|
||||||
|
// TODO check if something is missing
|
||||||
|
pg.update('messages m', { flags: pg.sql('t.flags::varchar(255)[]') })
|
||||||
|
.from('('+pg.sql.values(rows)+') AS t (uid, flags)')
|
||||||
|
.where({ 'm.folder_id': boxId }).where(pg.sql('m.uid=t.uid')).run(gen.ef());
|
||||||
|
self.synced += messages.length;
|
||||||
|
process.stderr.write('\rsynchronizing '+self.synced);
|
||||||
|
}
|
||||||
|
|
||||||
Syncer.saveMessages = function*(messages, boxId)
|
Syncer.saveMessages = function*(messages, boxId)
|
||||||
{
|
{
|
||||||
var self = this;
|
var self = this;
|
||||||
var err;
|
|
||||||
yield gen.throttle(2);
|
yield gen.throttle(2);
|
||||||
try
|
var uids = messages.map(m => m[1].uid);
|
||||||
{
|
var [ exist ] = yield pg.select('uid, flags').from('messages')
|
||||||
var uids = [];
|
.where({ folder_id: boxId }).where(pg.sql.in('uid', uids)).rows(gen.ef());
|
||||||
var uk = {};
|
uids = {};
|
||||||
for (var i = 0; i < messages.length; i++)
|
for (var i = 0; i < exist.length; i++)
|
||||||
{
|
uids[exist[i].uid] = true;
|
||||||
uids.push(messages[i][1].uid);
|
for (var i = 0; i < messages.length; i++)
|
||||||
uk[messages[i][1].uid] = messages[i];
|
if (!uids[messages[i][1].uid])
|
||||||
}
|
yield* this.addMessage(messages[i][0], messages[i][1]);
|
||||||
yield pg.update('messages', { flags: pg.sql('array_remove(flags, \'deleted\')') })
|
|
||||||
.where({ folder_id: boxId }).where(pg.sql.in('uid', uids)).run(gen.ef());
|
|
||||||
var [ exist ] = yield pg.select('uid, flags').from('messages')
|
|
||||||
.where({ folder_id: boxId }).where(pg.sql.in('uid', uids)).rows(gen.ef());
|
|
||||||
for (var i = 0; i < exist.length; i++)
|
|
||||||
{
|
|
||||||
var fl = uk[exist[i].uid][0].flags;
|
|
||||||
if (fl.sort().join('\\') != exist[i].flags.sort().join('\\'))
|
|
||||||
yield pg.update('messages', { flags: toPgArray(fl) }).where({ folder_id: boxId, uid: exist[i].uid }).run(gen.ef());
|
|
||||||
delete uk[exist[i].uid];
|
|
||||||
}
|
|
||||||
self.synced += exist.length;
|
|
||||||
process.stderr.write('\rsynchronizing '+self.synced);
|
|
||||||
var run = [];
|
|
||||||
for (var i in uk)
|
|
||||||
run.push(this.addMessage(uk[i][0], uk[i][1]));
|
|
||||||
if (run.length)
|
|
||||||
{
|
|
||||||
process.stderr.write('\n');
|
|
||||||
yield gen.runParallel(run, gen.cb());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (e)
|
|
||||||
{
|
|
||||||
err = e;
|
|
||||||
}
|
|
||||||
self.parsed -= messages.length;
|
|
||||||
if (self.paused && self.parsed < 20)
|
|
||||||
{
|
|
||||||
self.paused = false;
|
|
||||||
self.srv._parser._ignoreReadable = false;
|
|
||||||
process.nextTick(self.srv._parser._cbReadable);
|
|
||||||
}
|
|
||||||
if (err)
|
|
||||||
throw err;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Syncer.addMessage = function*(msgrow, attrs)
|
Syncer.addMessage = function*(msgrow, attrs)
|
||||||
|
|
Loading…
Reference in New Issue