First variant of flag change - synchronous
parent
b8634a9269
commit
54f872e264
76
Syncer.js
76
Syncer.js
|
@ -283,9 +283,11 @@ class Syncer
|
|||
}
|
||||
else
|
||||
{
|
||||
if (fetchState.flags[m.uid].join(',') != m.flags.join(','))
|
||||
// recent тут уже не будет
|
||||
let flags = m.flags.filter(f => f != 'recent').sort();
|
||||
if (fetchState.flags[m.uid].sort().join(',') != flags.join(','))
|
||||
{
|
||||
fetchState.updateFlags.push({ uid: m.uid, flags: toPgArray(m.flags) });
|
||||
fetchState.updateFlags.push({ uid: m.uid, flags: toPgArray(flags) });
|
||||
}
|
||||
delete fetchState.flags[m.uid];
|
||||
}
|
||||
|
@ -539,16 +541,13 @@ class Syncer
|
|||
bcc: (header.bcc||[]).map((a) => [ a.name, a.address ]),
|
||||
replyto: (header.replyTo||[]).map((a) => [ a.name, a.address ])[0],
|
||||
attachments: this.extractAttachments(attrs.struct),
|
||||
inout: (header.headers.received||[]).length ? 'in' : 'out',
|
||||
});
|
||||
msgrow.messageid = header.messageId || '';
|
||||
msgrow.inreplyto = header.inReplyTo && header.inReplyTo[0] || '';
|
||||
msgrow.time = header.date;
|
||||
msgrow.size = attrs.size;
|
||||
if (!header.headers.received || !header.headers.received.length)
|
||||
msgrow.flags.push('out');
|
||||
else
|
||||
msgrow.flags.push('in');
|
||||
msgrow.flags = toPgArray(msgrow.flags);
|
||||
msgrow.flags = toPgArray(msgrow.flags.filter(f => f != 'recent'));
|
||||
msgrow.refs = toPgArray(header.references);
|
||||
for (let i in msgrow)
|
||||
if (typeof msgrow[i] == 'string')
|
||||
|
@ -632,6 +631,69 @@ class Syncer
|
|||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// flags = lowercase and without \
|
||||
async processFlags(msgIds, action, flags)
|
||||
{
|
||||
flags = flags instanceof Array ? flags : [ flags ];
|
||||
const bad_flags = flags.filter(f => f != 'seen' && f != 'answered' && f != 'flagged' && f != 'deleted' && f != 'draft');
|
||||
if (bad_flags.length)
|
||||
{
|
||||
throw new Error('bad flags: '+bad_flags.join(', '));
|
||||
}
|
||||
let rows = await SQL.select(
|
||||
this.pg, { m: 'messages', f: 'folders' },
|
||||
'f.account_id, m.folder_id, f.name folder_name, m.uid',
|
||||
{ id: msgIds, 'f.id=m.folder_id': [] },
|
||||
{ order_by: 'm.folder_id, m.uid' }
|
||||
);
|
||||
if (!rows.length)
|
||||
{
|
||||
return;
|
||||
}
|
||||
let uids = [];
|
||||
if (!(action == 'add' || action == 'set' || action == 'del'))
|
||||
{
|
||||
throw new Error('processFlags: bad action = '+action);
|
||||
}
|
||||
for (let i = 0; i < rows.length; i++)
|
||||
{
|
||||
if (i == rows.length-1 || i > 0 && rows[i].folder_id != rows[i-1].folder_id)
|
||||
{
|
||||
let srv = await this.imap.getConnection(rows[i].account_id, rows[i].folder_name);
|
||||
await new Promise((r, j) => srv[action+'Flags'](uids, flags.map(f => '\\'+f.substr(0, 1).toUpperCase()+f.substr(1)), r));
|
||||
this.imap.releaseConnection(rows[i].account_id);
|
||||
uids = [];
|
||||
}
|
||||
else
|
||||
{
|
||||
uids.push(rows[i].uid);
|
||||
}
|
||||
}
|
||||
let upd = 'flags', bind = [];
|
||||
if (action == 'add')
|
||||
{
|
||||
for (let flag of flags)
|
||||
{
|
||||
upd = upd + ' || (case when flags @> array[?] then \'{}\' else array[?] end)';
|
||||
bind.push(flag, flag);
|
||||
}
|
||||
}
|
||||
else if (action == 'del')
|
||||
{
|
||||
for (let flag of flags)
|
||||
{
|
||||
upd = 'array_remove('+upd+', ?)';
|
||||
bind.push(flag);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
upd = 'array[' + flags.map(f => '?').join(', ') + ']::text[]';
|
||||
bind = [ ...flags ];
|
||||
}
|
||||
await SQL.update(this.pg, 'messages', { ['flags = '+upd]: bind }, { id: msgIds });
|
||||
}
|
||||
}
|
||||
|
||||
function toPgArray(a)
|
||||
|
|
25
SyncerWeb.js
25
SyncerWeb.js
|
@ -37,6 +37,7 @@ class SyncerWeb
|
|||
this.app.get('/messages', wrapAsync(this, 'get_messages'));
|
||||
this.app.get('/message', wrapAsync(this, 'get_message'));
|
||||
this.app.get('/attachment', wrapAsync(this, 'get_attachment'));
|
||||
this.app.post('/mark', wrapAsync(this, 'post_mark'));
|
||||
this.app.post('/sync', wrapAsync(this, 'post_sync'));
|
||||
this.syncer.events.on('sync', (params) => this.syncer_sync(params));
|
||||
}
|
||||
|
@ -120,18 +121,22 @@ class SyncerWeb
|
|||
.filter(f => f[1]);
|
||||
p['(f.account_id, f.name) NOT IN ('+folders.map(f => '(?, ?)').join(', ')+')'] =
|
||||
[].concat.apply([], folders);
|
||||
p['(flags @> array[\'in\'])'] = [];
|
||||
p['f.kind NOT IN (?, ?, ?)'] = [ 'sent', 'drafts', 'trash' ];
|
||||
}
|
||||
else if (query.folderType == 'sent')
|
||||
{
|
||||
// Все отправленные
|
||||
p['(flags @> array[\'out\'])'] = [];
|
||||
p['f.kind'] = 'sent';
|
||||
}
|
||||
else if (query.folderType == 'outbox')
|
||||
{
|
||||
// FIXME это "папка" для локально составленных сообщений, не сохранённых в IMAP
|
||||
}
|
||||
else if (query.folderType == 'drafts' || query.folderType == 'spam' || query.folderType == 'trash')
|
||||
else if (query.folderType == 'drafts' || query.folderType == 'trash')
|
||||
{
|
||||
p['f.kind'] = query.folderType;
|
||||
}
|
||||
else if (query.folderType == 'spam')
|
||||
{
|
||||
let folders = Object.keys(this.syncer.accounts)
|
||||
.map(id => [ id, this.syncer.accounts[id].settings.folders[query.folderType] ])
|
||||
|
@ -297,6 +302,20 @@ class SyncerWeb
|
|||
res.end();
|
||||
}
|
||||
|
||||
async post_mark(req, res)
|
||||
{
|
||||
if (this.cfg.login && (!req.session || !req.session.auth))
|
||||
{
|
||||
return res.sendStatus(401);
|
||||
}
|
||||
if (!req.query.msgId || !req.query.flag || req.query.flag == 'deleted')
|
||||
{
|
||||
return res.sendStatus(400);
|
||||
}
|
||||
await this.syncer.processFlags(req.query.msgId, req.query.del ? 'del' : 'add', req.query.flag);
|
||||
return res.send({ ok: true });
|
||||
}
|
||||
|
||||
syncer_sync(params)
|
||||
{
|
||||
this.io.emit('sync', params);
|
||||
|
|
|
@ -56,7 +56,6 @@ create table messages (
|
|||
-- FIXME bigint
|
||||
time timestamptz not null,
|
||||
size int not null,
|
||||
-- FIXME jsonb
|
||||
flags text[] not null,
|
||||
foreign key (folder_id) references folders (id) on delete cascade on update cascade
|
||||
);
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
/**
|
||||
* TODO:
|
||||
* + скачивание вложений
|
||||
* - сохранять заголовки в БД и возможно сохранять сообщения целиком ещё где-то
|
||||
* - пометка прочитанным, просмотренным (seen)
|
||||
* - фоновая индексация всех текстов сообщений в ящике
|
||||
* - написание сообщений
|
||||
|
|
Loading…
Reference in New Issue