2016-09-11 22:04:37 +03:00
|
|
|
|
const gen = require('gen-thread');
|
|
|
|
|
const Imap = require('imap');
|
|
|
|
|
const ImapManager = require('./ImapManager.js');
|
2016-10-03 16:55:09 +03:00
|
|
|
|
const EventEmitter = require('events').EventEmitter;
|
2016-10-05 01:46:35 +03:00
|
|
|
|
const MailParser = require('mailparser').MailParser;
|
2016-10-05 13:50:33 +03:00
|
|
|
|
const iconv = require('iconv-lite');
|
2016-09-11 22:04:37 +03:00
|
|
|
|
|
|
|
|
|
module.exports = Syncer;
|
|
|
|
|
|
|
|
|
|
function Syncer(pg)
|
|
|
|
|
{
|
|
|
|
|
this.syncInProgress = false;
|
|
|
|
|
this.pg = pg;
|
|
|
|
|
this.imap = new ImapManager();
|
2016-10-02 21:57:43 +03:00
|
|
|
|
this.runIdle = this.runIdle.bind(this);
|
|
|
|
|
this.stopIdle = this.stopIdle.bind(this);
|
2016-10-03 16:55:09 +03:00
|
|
|
|
this.events = new EventEmitter();
|
2016-09-11 22:04:37 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Syncer.prototype.init = function*(cfg)
|
|
|
|
|
{
|
|
|
|
|
for (var i = 0; i < cfg.accounts.length; i++)
|
|
|
|
|
yield* this.addAccount(cfg.accounts[i]);
|
|
|
|
|
yield* this.loadAccounts();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Syncer.prototype.syncAll = function*()
|
|
|
|
|
{
|
|
|
|
|
this.syncInProgress = true;
|
|
|
|
|
for (var id in this.accounts)
|
|
|
|
|
yield* this.syncAccount(this.accounts[id]);
|
|
|
|
|
this.syncInProgress = false;
|
2016-10-03 16:55:09 +03:00
|
|
|
|
this.events.emit('sync', { state: 'complete' });
|
2016-09-11 22:04:37 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Syncer.prototype.addAccount = function*(account)
|
|
|
|
|
{
|
|
|
|
|
var self = this;
|
|
|
|
|
var [ row ] = yield this.pg.select('id').from('accounts').where({ email: account.email }).rows(gen.ef());
|
|
|
|
|
if (row.length)
|
|
|
|
|
{
|
|
|
|
|
row = row[0];
|
2016-10-02 21:57:43 +03:00
|
|
|
|
yield this.pg.update('accounts', { settings: { imap: account.imap, folders: account.folders } }).where({ id: row.id }).run(gen.ef());
|
2016-09-11 22:04:37 +03:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
[ row ] = yield this.pg.insert('accounts', {
|
|
|
|
|
name: account.name,
|
|
|
|
|
email: account.email,
|
|
|
|
|
settings: {
|
2016-10-09 17:48:47 +03:00
|
|
|
|
imap: account.imap,
|
|
|
|
|
folders: account.folders
|
2016-09-11 22:04:37 +03:00
|
|
|
|
}
|
|
|
|
|
}).returning('*').row(gen.ef());
|
|
|
|
|
}
|
|
|
|
|
return row.id;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Syncer.prototype.loadAccounts = function*()
|
|
|
|
|
{
|
|
|
|
|
let [ rows ] = yield this.pg.select('*').from('accounts').rows(gen.ef());
|
|
|
|
|
this.accounts = {};
|
|
|
|
|
for (var i = 0; i < rows.length; i++)
|
|
|
|
|
{
|
|
|
|
|
this.accounts[rows[i].id] = rows[i];
|
|
|
|
|
this.imap.setServer(rows[i].id, rows[i].settings.imap);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-10-02 21:57:43 +03:00
|
|
|
|
Syncer.prototype.getSyncConnection = function*(accountId, boxName)
|
|
|
|
|
{
|
|
|
|
|
var srv = yield* this.imap.getConnection(accountId, null, 'S', this.runIdle, this.stopIdle);
|
|
|
|
|
return srv;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Syncer.prototype.idleUidvalidity = function(accountId, uidvalidity)
|
|
|
|
|
{
|
|
|
|
|
// uidvalidity changes (FUUUU) remove everything and resync
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Syncer.prototype.idleMail = function(accountId, count)
|
|
|
|
|
{
|
|
|
|
|
// <count> new messages arrived while idling, fetch them
|
|
|
|
|
var self = this;
|
|
|
|
|
gen.run(function*()
|
|
|
|
|
{
|
|
|
|
|
var srv = yield* self.getSyncConnection(accountId);
|
|
|
|
|
yield* self.syncBox(srv, accountId, 'INBOX');
|
|
|
|
|
self.releaseSyncConnection(accountId);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Syncer.prototype.idleVanish = function(accountId, uids)
|
|
|
|
|
{
|
|
|
|
|
// messages expunged by uids
|
|
|
|
|
var self = this;
|
|
|
|
|
gen.run(function*()
|
|
|
|
|
{
|
2016-10-05 15:27:03 +03:00
|
|
|
|
let [ boxId ] = yield self.pg.select('id').from('folders')
|
2016-10-02 21:57:43 +03:00
|
|
|
|
.where({ name: 'INBOX', account_id: accountId }).val(gen.ef());
|
|
|
|
|
yield* self.deleteVanished(boxId, uids);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Syncer.prototype.idleExpunge = function(accountId, seqno)
|
|
|
|
|
{
|
|
|
|
|
// message expunged by (FUUUU) sequence number(s?)
|
|
|
|
|
var self = this;
|
|
|
|
|
gen.run(function*()
|
|
|
|
|
{
|
|
|
|
|
var srv = yield* self.getSyncConnection(accountId);
|
|
|
|
|
yield* self.syncBox(srv, accountId, 'INBOX');
|
|
|
|
|
self.releaseSyncConnection(accountId);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Syncer.prototype.runIdle = function(accountId, srv)
|
|
|
|
|
{
|
|
|
|
|
var self = this;
|
|
|
|
|
if (!srv._idleCallbacks)
|
|
|
|
|
{
|
|
|
|
|
srv._idleCallbacks = {
|
|
|
|
|
uidvalidity: this.idleUidvalidity.bind(this, accountId),
|
|
|
|
|
mail: this.idleMail.bind(this, accountId),
|
|
|
|
|
vanish: this.idleVanish.bind(this, accountId),
|
|
|
|
|
expunge: this.idleExpunge.bind(this, accountId)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
for (var i in srv._idleCallbacks)
|
|
|
|
|
{
|
|
|
|
|
srv.on(i, srv._idleCallbacks[i]);
|
|
|
|
|
}
|
2016-10-03 17:06:47 +03:00
|
|
|
|
srv.openBox('INBOX', true, function() {});
|
2016-10-02 21:57:43 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Syncer.prototype.stopIdle = function(accountId, srv)
|
|
|
|
|
{
|
|
|
|
|
for (var i in srv._idleCallbacks)
|
|
|
|
|
{
|
|
|
|
|
srv.removeListener(i, srv._idleCallbacks[i]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-10-03 17:06:47 +03:00
|
|
|
|
Syncer.prototype.releaseSyncConnection = function(accountId, boxName)
|
2016-10-02 21:57:43 +03:00
|
|
|
|
{
|
|
|
|
|
this.imap.releaseConnection(accountId, 'S');
|
|
|
|
|
}
|
|
|
|
|
|
2016-09-11 22:04:37 +03:00
|
|
|
|
Syncer.prototype.syncAccount = function*(account)
|
|
|
|
|
{
|
|
|
|
|
var self = this;
|
|
|
|
|
var accountId;
|
|
|
|
|
var [ rows ] = yield this.pg.select('id').from('accounts').where({ email: account.email }).rows(gen.ef());
|
|
|
|
|
if (rows[0] && rows[0].id)
|
|
|
|
|
accountId = rows[0].id;
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
var [ row ] = yield this.pg.insert('accounts', {
|
|
|
|
|
name: account.name,
|
|
|
|
|
email: account.email,
|
|
|
|
|
settings: {
|
|
|
|
|
imap: account.imap
|
|
|
|
|
}
|
|
|
|
|
}).returning('id').row(gen.ef());
|
|
|
|
|
accountId = row.id;
|
|
|
|
|
}
|
2016-10-02 21:57:43 +03:00
|
|
|
|
var srv = yield* self.getSyncConnection(accountId);
|
2016-09-11 22:04:37 +03:00
|
|
|
|
var [ boxes ] = yield srv.getBoxes(gen.ef());
|
|
|
|
|
for (var k in boxes)
|
|
|
|
|
{
|
|
|
|
|
var boxKind = (boxes[k].special_use_attrib || '').replace('\\', '').toLowerCase();
|
|
|
|
|
yield* self.syncBox(srv, accountId, k, boxKind, true);
|
|
|
|
|
}
|
2016-10-02 21:57:43 +03:00
|
|
|
|
self.releaseSyncConnection(accountId);
|
2016-09-11 22:04:37 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Syncer.prototype.syncBox = function*(srv, accountId, boxName, boxKind, doFull)
|
|
|
|
|
{
|
|
|
|
|
var [ boxStatus ] = yield srv.openBox(boxName, true, gen.ef());
|
|
|
|
|
|
|
|
|
|
// IMAP sync: http://tools.ietf.org/html/rfc4549
|
|
|
|
|
var [ boxRow ] = yield this.pg.select('*').from('folders')
|
|
|
|
|
.where({ account_id: accountId, name: boxStatus.name }).rows(gen.ef());
|
|
|
|
|
if (boxRow.length)
|
|
|
|
|
{
|
|
|
|
|
boxRow = boxRow[0];
|
|
|
|
|
if (boxRow.uidvalidity != boxStatus.uidvalidity)
|
|
|
|
|
{
|
|
|
|
|
yield* this.deleteMessages(this.pg.sql.and({ folder_id: boxRow.id }, this.pg.sql('uid is not null')));
|
|
|
|
|
boxRow.uidvalidity = boxStatus.uidvalidity;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
[ boxRow ] = yield this.pg.insert('folders', {
|
|
|
|
|
name: boxStatus.name,
|
|
|
|
|
uidvalidity: boxStatus.uidvalidity,
|
|
|
|
|
account_id: accountId,
|
|
|
|
|
highestmodseq: 0,
|
|
|
|
|
kind: boxKind||''
|
|
|
|
|
}).returning('id').row(gen.ef());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// fetch new messages
|
|
|
|
|
var missing = [];
|
|
|
|
|
var [ maxUid ] = yield this.pg.select('MAX(uid)').from('messages')
|
|
|
|
|
.where({ folder_id: boxRow.id }).val(gen.ef());
|
2016-10-05 01:46:35 +03:00
|
|
|
|
if (boxRow.highestmodseq)
|
2016-09-11 22:04:37 +03:00
|
|
|
|
{
|
2016-10-03 16:55:09 +03:00
|
|
|
|
this.events.emit('sync', { state: 'start', quick: true, email: this.accounts[accountId].email, folder: boxRow.name });
|
2016-09-11 22:04:37 +03:00
|
|
|
|
process.stderr.write(this.accounts[accountId].email+'/'+boxRow.name+': quick resync\n');
|
|
|
|
|
yield* this.quickResync(srv, boxRow.id, maxUid, boxRow.highestmodseq, missing);
|
|
|
|
|
boxRow.highestmodseq = boxStatus.highestmodseq;
|
|
|
|
|
}
|
|
|
|
|
else if (doFull && maxUid)
|
|
|
|
|
{
|
|
|
|
|
// list messages, update flags and version tag
|
2016-10-03 16:55:09 +03:00
|
|
|
|
this.events.emit('sync', { state: 'start', email: this.accounts[accountId].email, folder: boxRow.name });
|
2016-09-11 22:04:37 +03:00
|
|
|
|
process.stderr.write(this.accounts[accountId].email+'/'+boxRow.name+': full resync\n');
|
2016-10-03 16:55:09 +03:00
|
|
|
|
yield* this.fullResync(srv, boxRow.id, maxUid, missing, boxStatus.messages.total);
|
2016-09-11 22:04:37 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
missing.push((maxUid ? maxUid+1 : 1)+':*');
|
|
|
|
|
yield* this.imap.runFetch(srv, missing, {
|
|
|
|
|
size: true,
|
2016-10-05 01:57:10 +03:00
|
|
|
|
bodies: 'HEADER',
|
|
|
|
|
struct: true,
|
2016-09-11 22:04:37 +03:00
|
|
|
|
}, (messages, state) => this.saveMessages(messages, boxRow.id, state));
|
|
|
|
|
|
|
|
|
|
yield this.pg.update('folders', {
|
2016-10-05 01:46:35 +03:00
|
|
|
|
uidvalidity: boxStatus.uidvalidity,
|
|
|
|
|
highestmodseq: boxStatus.highestmodseq||0
|
2016-09-11 22:04:37 +03:00
|
|
|
|
}).where({ id: boxRow.id }).run(gen.ef());
|
|
|
|
|
}
|
|
|
|
|
|
2016-10-03 16:55:09 +03:00
|
|
|
|
Syncer.prototype.fullResync = function*(srv, boxId, maxUid, missing, total)
|
2016-09-11 22:04:37 +03:00
|
|
|
|
{
|
|
|
|
|
var [ flags ] = yield this.pg.select('uid, flags').from('messages').where({ folder_id: boxId }).rows(gen.ef());
|
|
|
|
|
flags = flags.reduce((o, row) => { o[row.uid] = row.flags; return o; }, {});
|
|
|
|
|
|
|
|
|
|
var updateFlags = [];
|
|
|
|
|
|
|
|
|
|
process.stderr.write('\rsynchronizing 0');
|
|
|
|
|
yield* this.imap.runFetch(
|
|
|
|
|
srv, '1:'+maxUid, {},
|
|
|
|
|
(messages, state) => this.queueFlags(messages, boxId, state),
|
2016-10-09 18:56:04 +03:00
|
|
|
|
{ flags: flags, updateFlags: updateFlags, missing: missing||[], total: total, nopause: true }
|
2016-09-11 22:04:37 +03:00
|
|
|
|
);
|
|
|
|
|
process.stderr.write('\n');
|
2016-10-03 16:55:09 +03:00
|
|
|
|
this.events.emit('sync', { state: 'finish-box' });
|
2016-09-11 22:04:37 +03:00
|
|
|
|
|
|
|
|
|
yield* this.updateFlags(boxId, updateFlags);
|
|
|
|
|
|
|
|
|
|
// delete messages removed from IMAP server
|
|
|
|
|
flags = Object.keys(flags);
|
|
|
|
|
if (flags.length)
|
|
|
|
|
yield* this.deleteMessages(this.pg.sql.and({ folder_id: boxId }, this.pg.sql.in('uid', flags)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Syncer.prototype.queueFlags = function*(messages, boxId, fetchState)
|
|
|
|
|
{
|
|
|
|
|
for (var i = 0; i < messages.length; i++)
|
|
|
|
|
{
|
|
|
|
|
var m = messages[i][0];
|
|
|
|
|
if (!fetchState.flags[m.uid])
|
|
|
|
|
fetchState.missing.push(m.uid);
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
if (fetchState.flags[m.uid].join(',') != m.flags.join(','))
|
|
|
|
|
fetchState.updateFlags.push({ uid: m.uid, flags: toPgArray(m.flags) });
|
|
|
|
|
delete fetchState.flags[m.uid];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
fetchState.synced += messages.length;
|
2016-10-03 16:55:09 +03:00
|
|
|
|
this.events.emit('sync', { state: 'progress', done: fetchState.synced, total: fetchState.total });
|
2016-09-11 22:04:37 +03:00
|
|
|
|
process.stderr.write('\rsynchronizing '+fetchState.synced);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Syncer.prototype.updateFlags = function*(boxId, updateFlags, checkMissing)
|
|
|
|
|
{
|
|
|
|
|
if (updateFlags.length)
|
|
|
|
|
{
|
|
|
|
|
var sql = this.pg.update('messages m', { flags: this.pg.sql('t.flags::varchar(255)[]') })
|
|
|
|
|
.from('('+this.pg.sql.values(updateFlags)+') AS t (uid, flags)')
|
|
|
|
|
.where({ 'm.folder_id': boxId }).where(this.pg.sql('m.uid=t.uid'));
|
|
|
|
|
if (checkMissing)
|
|
|
|
|
{
|
|
|
|
|
var [ updated ] = yield sql.returning('m.uid').rows(gen.ef());
|
|
|
|
|
var missing = {};
|
|
|
|
|
for (var i = 0; i < updateFlags.length; i++)
|
|
|
|
|
missing[updateFlags[i].uid] = true;
|
|
|
|
|
for (var i = 0; i < updated.length; i++)
|
|
|
|
|
delete missing[updated[i].uid];
|
|
|
|
|
return Object.keys(missing);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
yield sql.run(gen.ef());
|
|
|
|
|
}
|
|
|
|
|
return [];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Syncer.prototype.quickResync = function*(srv, boxId, maxUid, changedSince, missing)
|
|
|
|
|
{
|
|
|
|
|
var updateFlags = [];
|
|
|
|
|
var vanished = [];
|
|
|
|
|
var onVanish = function(dias)
|
|
|
|
|
{
|
|
|
|
|
vanished = vanished.concat(vanished, dias);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
srv.on('vanish', onVanish);
|
|
|
|
|
yield* this.imap.runFetch(
|
|
|
|
|
srv, '1:'+maxUid, { modifiers: { changedsince: changedSince+' VANISHED' } },
|
2016-10-02 21:57:43 +03:00
|
|
|
|
(messages, state) => this.queueQuickFlags(messages, boxId, state), { updateFlags: updateFlags }
|
2016-09-11 22:04:37 +03:00
|
|
|
|
);
|
|
|
|
|
srv.removeListener('vanish', onVanish);
|
|
|
|
|
var checkedMissing = yield* this.updateFlags(boxId, updateFlags, missing && true);
|
|
|
|
|
if (missing)
|
|
|
|
|
missing.push.apply(missing, checkedMissing);
|
|
|
|
|
|
|
|
|
|
if (vanished.length)
|
|
|
|
|
{
|
2016-10-02 21:57:43 +03:00
|
|
|
|
yield* this.deleteVanished(boxId, vanished);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Syncer.prototype.deleteVanished = function*(boxId, vanished)
|
|
|
|
|
{
|
|
|
|
|
let lst = [], dia = [];
|
|
|
|
|
for (let i = 0; i < vanished.length; i++)
|
|
|
|
|
{
|
|
|
|
|
if (vanished[i][1])
|
|
|
|
|
dia.push('uid >= '+vanished[i][0]+' AND uid <= '+vanished[i][1]);
|
|
|
|
|
else
|
|
|
|
|
lst.push(vanished[i][0]);
|
2016-09-11 22:04:37 +03:00
|
|
|
|
}
|
2016-10-02 21:57:43 +03:00
|
|
|
|
if (lst.length)
|
|
|
|
|
dia.push('uid IN ('+lst.join(',')+')');
|
|
|
|
|
yield* this.deleteMessages(this.pg.sql.and({ folder_id: boxId }, this.pg.sql('('+dia.join(' OR ')+')')));
|
2016-09-11 22:04:37 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Syncer.prototype.queueQuickFlags = function*(messages, boxId, fetchState)
|
|
|
|
|
{
|
|
|
|
|
for (var i = 0; i < messages.length; i++)
|
|
|
|
|
{
|
|
|
|
|
var m = messages[i][0];
|
|
|
|
|
fetchState.updateFlags.push({ uid: m.uid, flags: toPgArray(m.flags) });
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Syncer.prototype.deleteMessages = function*(where)
|
|
|
|
|
{
|
|
|
|
|
yield this.pg.update('threads', { first_msg: null })
|
|
|
|
|
.where(this.pg.sql('first_msg IN ('+this.pg.select('id').from('messages').where(where)+')'))
|
|
|
|
|
.run(gen.ef());
|
|
|
|
|
yield this.pg.delete('messages').where(where).run(gen.ef());
|
|
|
|
|
yield this.pg.update('threads',
|
|
|
|
|
{ first_msg: this.pg.sql('('+
|
|
|
|
|
this.pg.select('id').from('messages').where({ thread_id: this.pg.sql('threads.id') }).orderBy('time').limit(1)
|
|
|
|
|
+')') }).where(this.pg.sql('first_msg IS NULL')).run(gen.ef());
|
|
|
|
|
yield this.pg.delete('threads').where(this.pg.sql('first_msg IS NULL')).run(gen.ef());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Syncer.prototype.saveMessages = function*(messages, boxId)
|
|
|
|
|
{
|
|
|
|
|
var self = this;
|
|
|
|
|
yield gen.throttle(2);
|
|
|
|
|
var uids = messages.map(m => m[1].uid);
|
|
|
|
|
var [ exist ] = yield this.pg.select('uid, flags').from('messages')
|
|
|
|
|
.where({ folder_id: boxId }).where(this.pg.sql.in('uid', uids)).rows(gen.ef());
|
|
|
|
|
uids = {};
|
|
|
|
|
for (var i = 0; i < exist.length; i++)
|
|
|
|
|
uids[exist[i].uid] = true;
|
|
|
|
|
for (var i = 0; i < messages.length; i++)
|
|
|
|
|
if (!uids[messages[i][1].uid])
|
|
|
|
|
yield* this.addMessage(boxId, messages[i][0], messages[i][1]);
|
|
|
|
|
}
|
|
|
|
|
|
2016-10-05 01:46:35 +03:00
|
|
|
|
Syncer.prototype.parseMsg = function*(msg)
|
|
|
|
|
{
|
2016-10-05 13:50:33 +03:00
|
|
|
|
var parser = new MailParser({ streamAttachments: false, defaultCharset: 'windows-1251' });
|
2016-10-05 01:46:35 +03:00
|
|
|
|
parser.once('end', gen.cb());
|
|
|
|
|
parser.write(msg);
|
|
|
|
|
var [ obj ] = yield parser.end();
|
|
|
|
|
return obj;
|
|
|
|
|
}
|
|
|
|
|
|
2016-10-05 01:57:10 +03:00
|
|
|
|
Syncer.prototype.extractAttachments = function(struct, attachments)
|
|
|
|
|
{
|
|
|
|
|
attachments = attachments || [];
|
|
|
|
|
for (var i = 0; i < struct.length; i++)
|
|
|
|
|
{
|
|
|
|
|
if (struct[i] instanceof Array)
|
|
|
|
|
this.extractAttachments(struct[i], attachments);
|
|
|
|
|
else if (struct[i].disposition && struct[i].disposition.type == 'attachment')
|
|
|
|
|
{
|
|
|
|
|
attachments.push([
|
|
|
|
|
struct[i].disposition.params && struct[i].disposition.params.filename || struct[i].description || '',
|
|
|
|
|
struct[i].type+'/'+struct[i].subtype,
|
|
|
|
|
struct[i].size
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return attachments;
|
|
|
|
|
}
|
|
|
|
|
|
2016-09-11 22:04:37 +03:00
|
|
|
|
Syncer.prototype.addMessage = function*(boxId, msgrow, attrs)
|
|
|
|
|
{
|
|
|
|
|
var self = this;
|
|
|
|
|
var pgtx, end_transaction;
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
[ pgtx, end_transaction ] = yield this.pg.transaction(gen.cb(), function(e) { if (e) throw e; });
|
|
|
|
|
|
2016-10-05 01:46:35 +03:00
|
|
|
|
let header = yield* this.parseMsg(msgrow.headers);
|
|
|
|
|
header.references = header.references || [];
|
2016-09-11 22:04:37 +03:00
|
|
|
|
if (header.references.length)
|
|
|
|
|
{
|
2016-10-05 01:46:35 +03:00
|
|
|
|
if (!header.inReplyTo || !header.inReplyTo[0])
|
|
|
|
|
header.inReplyTo = [ header.references[header.references.length-1] ];
|
|
|
|
|
else if (header.references[header.references.length-1] != header.inReplyTo[0])
|
|
|
|
|
header.references.push(header.inReplyTo[0]);
|
2016-09-11 22:04:37 +03:00
|
|
|
|
}
|
|
|
|
|
if (!header.date)
|
|
|
|
|
header.date = new Date(attrs.date);
|
|
|
|
|
|
2016-10-05 13:50:33 +03:00
|
|
|
|
if (JSON.stringify(header).indexOf('<27>') >= 0)
|
|
|
|
|
{
|
|
|
|
|
// Charset error!
|
|
|
|
|
console.log(iconv.decode(msgrow.headers, 'cp1251'));
|
|
|
|
|
console.log(header);
|
|
|
|
|
}
|
|
|
|
|
|
2016-10-05 01:46:35 +03:00
|
|
|
|
delete msgrow.headers;
|
2016-09-11 22:04:37 +03:00
|
|
|
|
msgrow.folder_id = boxId;
|
2016-10-05 01:46:35 +03:00
|
|
|
|
msgrow.subject = header.subject || '';
|
|
|
|
|
msgrow.props = JSON.stringify({
|
|
|
|
|
from: ((header.from||[]).map((a) => [ a.name, a.address ]))[0],
|
|
|
|
|
to: (header.to||[]).map((a) => [ a.name, a.address ]),
|
|
|
|
|
cc: (header.cc||[]).map((a) => [ a.name, a.address ]),
|
|
|
|
|
bcc: (header.bcc||[]).map((a) => [ a.name, a.address ]),
|
2016-10-05 12:51:11 +03:00
|
|
|
|
replyto: (header.replyTo||[]).map((a) => [ a.name, a.address ])[0],
|
2016-10-05 01:57:10 +03:00
|
|
|
|
attachments: this.extractAttachments(attrs.struct),
|
2016-10-05 01:46:35 +03:00
|
|
|
|
});
|
|
|
|
|
msgrow.messageid = header.messageId || '';
|
|
|
|
|
msgrow.inreplyto = header.inReplyTo && header.inReplyTo[0] || '';
|
2016-09-11 22:04:37 +03:00
|
|
|
|
msgrow.time = header.date;
|
2016-10-05 01:46:35 +03:00
|
|
|
|
msgrow.size = attrs.size;
|
2016-10-05 12:51:11 +03:00
|
|
|
|
if (!header.headers.received || !header.headers.received.length)
|
2016-10-09 17:48:47 +03:00
|
|
|
|
msgrow.flags.push('out');
|
|
|
|
|
else
|
|
|
|
|
msgrow.flags.push('in');
|
2016-09-11 22:04:37 +03:00
|
|
|
|
msgrow.flags = toPgArray(msgrow.flags);
|
|
|
|
|
msgrow.refs = toPgArray(header.references);
|
2016-10-05 01:46:35 +03:00
|
|
|
|
for (let i in msgrow)
|
|
|
|
|
if (typeof msgrow[i] == 'string')
|
|
|
|
|
msgrow[i] = msgrow[i].replace(/\x00/g, '');
|
2016-09-11 22:04:37 +03:00
|
|
|
|
|
|
|
|
|
var thisIsFirst = false;
|
|
|
|
|
if (header.references.length)
|
|
|
|
|
{
|
|
|
|
|
let [ threadId ] = yield pgtx.select('MAX(thread_id)').from('messages')
|
|
|
|
|
.where(this.pg.sql.in('messageid', header.references)).val(gen.ef());
|
|
|
|
|
if (!threadId)
|
|
|
|
|
{
|
|
|
|
|
[ threadId ] = yield pgtx.select('MAX(thread_id)').from('messages')
|
|
|
|
|
.where(new this.pg.sql.Binary('@>', 'refs', toPgArray([msgrow.messageid]))).val(gen.ef());
|
|
|
|
|
if (threadId)
|
|
|
|
|
thisIsFirst = true;
|
|
|
|
|
}
|
|
|
|
|
msgrow.thread_id = threadId;
|
|
|
|
|
}
|
2016-10-05 01:46:35 +03:00
|
|
|
|
console.log(msgrow.time+' '+(header.from && header.from[0] && header.from[0].address || '?')+' '+msgrow.subject);
|
2016-09-11 22:04:37 +03:00
|
|
|
|
[ msgrow.id ] = yield pgtx.insert('messages', msgrow).returning('id').val(gen.ef());
|
|
|
|
|
if (!msgrow.thread_id)
|
|
|
|
|
{
|
|
|
|
|
[ msgrow.thread_id ] = yield pgtx.insert('threads', {
|
|
|
|
|
first_msg: msgrow.id,
|
|
|
|
|
msg_count: 1
|
|
|
|
|
}).returning('id').val(gen.ef());
|
|
|
|
|
yield pgtx.update('messages', { thread_id: msgrow.thread_id }).where({ id: msgrow.id }).run(gen.ef());
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
let upd = pgtx.update('threads', { msg_count: this.pg.sql('msg_count+1') });
|
|
|
|
|
if (thisIsFirst)
|
|
|
|
|
upd.first_msg = msgrow.id;
|
|
|
|
|
yield upd.where({ id: msgrow.threadId }).run(gen.ef());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
end_transaction();
|
|
|
|
|
}
|
|
|
|
|
catch (e0)
|
|
|
|
|
{
|
|
|
|
|
if (end_transaction)
|
|
|
|
|
end_transaction();
|
|
|
|
|
throw e0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function toPgArray(a)
|
|
|
|
|
{
|
|
|
|
|
a = JSON.stringify(a);
|
|
|
|
|
return '{'+a.substring(1, a.length-1)+'}';
|
|
|
|
|
}
|