diff --git a/ImapManager.js b/ImapManager.js index dc86cfb..a9db0c4 100644 --- a/ImapManager.js +++ b/ImapManager.js @@ -40,7 +40,7 @@ class ImapManager if (boxName && this.selected[connKey] != boxName) { // select different box - await new Promise((r, e) => this.connections[connKey].openBox(boxName, true, r)); + await new Promise((r, e) => this.connections[connKey].openBox(boxName, false, r)); this.selected[connKey] = boxName; } this.busy[connKey] = true; @@ -79,7 +79,7 @@ class ImapManager if (boxName) { - await new Promise((r, e) => srv.openBox(boxName, true, r)); + await new Promise((r, e) => srv.openBox(boxName, false, r)); this.selected[connKey] = boxName; } @@ -253,10 +253,7 @@ class ImapManager }); await new Promise((r, e) => msg.once('end', r)); msgrow.uid = attrs.uid; - msgrow.flags = attrs.flags.map(f => f[0] == '\\' ? f.toLowerCase().replace(/^\\/, '') : f.replace(/^\$*/, '$')); - let nf = msgrow.flags.filter(f => f != 'seen'); - nf = nf.length == msgrow.flags.length ? nf.concat(['unread']) : nf; - msgrow.flags = nf.sort(); + msgrow.flags = attrs.flags.map(f => f[0] == '\\' ? f.toLowerCase().replace(/^\\/, '') : f.replace(/^\$*/, '$')).sort(); return [ msgrow, attrs ]; } } diff --git a/Syncer.js b/Syncer.js index 559a9d9..35803fe 100644 --- a/Syncer.js +++ b/Syncer.js @@ -283,9 +283,8 @@ class Syncer } else { - // recent тут уже не будет - let flags = m.flags.filter(f => f != 'recent').sort(); - if (fetchState.flags[m.uid].sort().join(',') != flags.join(',')) + let flags = this.transformFlags(m.flags); + if (fetchState.flags[m.uid].join(',') != flags.join(',')) { fetchState.updateFlags.push({ uid: m.uid, flags: toPgArray(flags) }); } @@ -293,7 +292,11 @@ class Syncer } } fetchState.synced += messages.length; - //this.events.emit('sync', { state: 'progress', done: fetchState.synced, total: fetchState.total }); + if (fetchState.synced-(fetchState.prevSynced||0) >= fetchState.total/100) + { + this.events.emit('sync', { state: 'progress', done: fetchState.synced, total: fetchState.total }); + fetchState.prevSynced = fetchState.synced; + } process.stderr.write('\rsynchronizing '+fetchState.synced); } @@ -474,6 +477,20 @@ class Syncer return msg; } + transformFlags(flags) + { + // the absence of 'seen' is transformed into the added 'unread' flag + // 'unseen' is something that mail.ru adds by itself + // 'unread' is removed to not mess up with our 'unread' + flags = flags.filter(f => f != 'unseen' && f != 'recent' && f != 'unread'); + if (!flags.filter(f => f == 'seen').length) + flags = [ ...flags, 'unread' ]; + else + flags = flags.filter(f => f != 'seen'); + flags = flags.sort(); + return flags; + } + extractAttachments(struct, attachments) { attachments = attachments || []; @@ -547,7 +564,7 @@ class Syncer msgrow.inreplyto = header.inReplyTo && header.inReplyTo[0] || ''; msgrow.time = header.date; msgrow.size = attrs.size; - msgrow.flags = toPgArray(msgrow.flags.filter(f => f != 'recent')); + msgrow.flags = toPgArray(this.transformFlags(msgrow.flags)); msgrow.refs = toPgArray(header.references); for (let i in msgrow) if (typeof msgrow[i] == 'string') @@ -644,7 +661,7 @@ class Syncer 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': [] }, + { 'm.id': msgIds, 'f.id=m.folder_id': [] }, { order_by: 'm.folder_id, m.uid' } ); if (!rows.length) @@ -658,41 +675,63 @@ class Syncer } for (let i = 0; i < rows.length; i++) { + uids.push(rows[i].uid); 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)); + await new Promise((ok, no) => srv[action+'Flags']( + uids, + flags.map(f => '\\'+f.substr(0, 1).toUpperCase()+f.substr(1)), + (err, res) => err ? no(err) : ok(res) + )); 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); + if (flag == 'seen') + { + // instead of the 'seen' flag we store the absence of 'unread' + upd = 'array_remove(' + upd + ', ?)'; + bind.push('unread'); + } + else + { + 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); + if (flag == 'seen') + { + // instead of the absence of 'seen' flag we store 'unread' + upd = upd + ' || (case when flags @> array[?] then \'{}\' else array[?] end)'; + bind.push('unread', 'unread'); + } + else + { + upd = 'array_remove('+upd+', ?)'; + bind.push(flag); + } } } else { + flags = flags.filter(f => f == 'seen').length > 0 + ? flags.filter(f => f != 'seen') + : [ ...flags, 'unread' ]; upd = 'array[' + flags.map(f => '?').join(', ') + ']::text[]'; bind = [ ...flags ]; } - await SQL.update(this.pg, 'messages', { ['flags = '+upd]: bind }, { id: msgIds }); + await SQL.update(this.pg, 'messages m', { ['flags = '+upd]: bind }, { 'm.id': msgIds }); } } diff --git a/SyncerWeb.js b/SyncerWeb.js index 8f1c9a6..771db1a 100644 --- a/SyncerWeb.js +++ b/SyncerWeb.js @@ -77,11 +77,14 @@ class SyncerWeb 'select id, name, email, settings->\'folders\' folderMap,'+ ' (select count(*) from messages m, folders f'+ ' where m.folder_id=f.id and f.account_id=a.id'+ - ' and (flags @> array[\'pinned\',\'unread\'])) pinned_unread_count'+ + ' and (flags @> array[\'flagged\',\'unread\'])) pinned_unread_count, '+ + ' (select count(*) from messages m, folders f'+ + ' where m.folder_id=f.id and f.account_id=a.id'+ + ' and (flags @> array[\'flagged\'])) pinned_count'+ ' from accounts a' )).rows; const folders = (await this.pg.query( - 'select id, account_id, name,'+ + 'select id, account_id, name, kind,'+ ' (select count(*) from messages m where m.folder_id=f.id) total_count,'+ ' (select count(*) from messages m where m.folder_id=f.id and (flags @> array[\'unread\'])) unread_count'+ ' from folders f order by account_id, name' @@ -106,15 +109,11 @@ class SyncerWeb { p['m.folder_id'] = query.folderId; } - else if (query.folderType == 'unread') - { - p['(flags @> array[\'unread\'])'] = []; - } else if (query.folderType == 'pinned') { p['(flags @> array[\'flagged\'])'] = []; } - else if (query.folderType == 'inbox') + else if (query.folderType == 'inbox' || query.folderType == 'unread') { let folders = Object.keys(this.syncer.accounts) .map(id => [ id, this.syncer.accounts[id].settings.folders.spam ]) @@ -122,6 +121,10 @@ class SyncerWeb p['(f.account_id, f.name) NOT IN ('+folders.map(f => '(?, ?)').join(', ')+')'] = [].concat.apply([], folders); p['f.kind NOT IN (?, ?, ?)'] = [ 'sent', 'drafts', 'trash' ]; + if (query.folderType == 'unread') + { + p['(flags @> array[\'unread\'])'] = []; + } } else if (query.folderType == 'sent') {