diff --git a/internal/bridge/release_notes.go b/internal/bridge/release_notes.go index ff2d2960..a3a80094 100644 --- a/internal/bridge/release_notes.go +++ b/internal/bridge/release_notes.go @@ -15,7 +15,7 @@ // You should have received a copy of the GNU General Public License // along with ProtonMail Bridge. If not, see . -// Code generated by ./release-notes.sh at Mon Apr 6 08:14:14 CEST 2020. DO NOT EDIT. +// Code generated by ./release-notes.sh at Mon Apr 6 10:56:36 CEST 2020. DO NOT EDIT. package bridge diff --git a/internal/imap/mailbox.go b/internal/imap/mailbox.go index bd7ca316..56947507 100644 --- a/internal/imap/mailbox.go +++ b/internal/imap/mailbox.go @@ -125,11 +125,12 @@ func (im *imapMailbox) Status(items []imap.StatusItem) (*imap.MailboxStatus, err message.ThunderbirdNonJunkFlag, } - dbTotal, dbUnread, err := im.storeMailbox.GetCounts() - l.Debugln("DB: total", dbTotal, "unread", dbUnread, "err", err) + dbTotal, dbUnread, dbUnreadSeqNum, err := im.storeMailbox.GetCounts() + l.Debugln("DB: total", dbTotal, "unread", dbUnread, "unreadSeqNum", dbUnreadSeqNum, "err", err) if err == nil { status.Messages = uint32(dbTotal) status.Unseen = uint32(dbUnread) + status.UnseenSeqNum = uint32(dbUnreadSeqNum) } if status.UidNext, err = im.storeMailbox.GetNextUID(); err != nil { diff --git a/internal/imap/store.go b/internal/imap/store.go index ee63908c..5cab6e9d 100644 --- a/internal/imap/store.go +++ b/internal/imap/store.go @@ -68,7 +68,7 @@ type storeMailboxProvider interface { GetAPIIDsFromSequenceRange(start, stop uint32) ([]string, error) GetLatestAPIID() (string, error) GetNextUID() (uint32, error) - GetCounts() (dbTotal, dbUnread uint, err error) + GetCounts() (dbTotal, dbUnread, dbUnreadSeqNum uint, err error) GetUIDList(apiIDs []string) *uidplus.OrderedSeq GetUIDByHeader(header *mail.Header) uint32 GetDelimiter() string diff --git a/internal/store/change.go b/internal/store/change.go index 47d29778..88d69f78 100644 --- a/internal/store/change.go +++ b/internal/store/change.go @@ -76,18 +76,20 @@ func (store *Store) imapDeleteMessage(address, mailboxName string, sequenceNumbe store.imapSendUpdate(update) } -func (store *Store) imapMailboxStatus(address, mailboxName string, total, unread uint) { +func (store *Store) imapMailboxStatus(address, mailboxName string, total, unread, unreadSeqNum uint) { store.log.WithFields(logrus.Fields{ - "address": address, - "mailbox": mailboxName, - "total": total, - "unread": unread, + "address": address, + "mailbox": mailboxName, + "total": total, + "unread": unread, + "unreadSeqNum": unreadSeqNum, }).Trace("IDLE status") update := new(imapBackend.MailboxUpdate) update.Update = imapBackend.NewUpdate(address, mailboxName) update.MailboxStatus = imap.NewMailboxStatus(mailboxName, []imap.StatusItem{imap.StatusMessages, imap.StatusUnseen}) update.MailboxStatus.Messages = uint32(total) update.MailboxStatus.Unseen = uint32(unread) + update.MailboxStatus.UnseenSeqNum = uint32(unreadSeqNum) store.imapSendUpdate(update) } diff --git a/internal/store/change_test.go b/internal/store/change_test.go index 377205fb..ce39d0a6 100644 --- a/internal/store/change_test.go +++ b/internal/store/change_test.go @@ -105,8 +105,8 @@ func checkMessageUpdate(username, mailbox string, seqNum, uid int) func(interfac return func(update interface{}) bool { switch u := update.(type) { case *imapBackend.MessageUpdate: - return (u.Update.Username == username && - u.Update.Mailbox == mailbox && + return (u.Update.Username() == username && + u.Update.Mailbox() == mailbox && u.Message.SeqNum == uint32(seqNum) && u.Message.Uid == uint32(uid)) default: @@ -119,8 +119,8 @@ func checkMessageDelete(username, mailbox string, seqNum int) func(interface{}) return func(update interface{}) bool { switch u := update.(type) { case *imapBackend.ExpungeUpdate: - return (u.Update.Username == username && - u.Update.Mailbox == mailbox && + return (u.Update.Username() == username && + u.Update.Mailbox() == mailbox && u.SeqNum == uint32(seqNum)) default: return false diff --git a/internal/store/mailbox.go b/internal/store/mailbox.go index 967775e0..c4a834fb 100644 --- a/internal/store/mailbox.go +++ b/internal/store/mailbox.go @@ -81,7 +81,7 @@ func syncDraftsIfNecssary(tx *bolt.Tx, mb *Mailbox) { //nolint[funlen] // If the drafts mailbox total is non-zero, it means it has already been used // and there is no need to continue. Otherwise, we may need to do an initial sync. - total, _, err := mb.txGetCounts(tx) + total, _, _, err := mb.txGetCounts(tx) if err != nil || total != 0 { return } diff --git a/internal/store/mailbox_counts.go b/internal/store/mailbox_counts.go index 956e0e69..3674a1d8 100644 --- a/internal/store/mailbox_counts.go +++ b/internal/store/mailbox_counts.go @@ -28,15 +28,15 @@ import ( ) // GetCounts returns numbers of total and unread messages in this mailbox bucket. -func (storeMailbox *Mailbox) GetCounts() (total, unread uint, err error) { +func (storeMailbox *Mailbox) GetCounts() (total, unread, unseenSeqNum uint, err error) { err = storeMailbox.db().View(func(tx *bolt.Tx) error { - total, unread, err = storeMailbox.txGetCounts(tx) + total, unread, unseenSeqNum, err = storeMailbox.txGetCounts(tx) return err }) return } -func (storeMailbox *Mailbox) txGetCounts(tx *bolt.Tx) (total, unread uint, err error) { +func (storeMailbox *Mailbox) txGetCounts(tx *bolt.Tx) (total, unread, unseenSeqNum uint, err error) { // For total it would be enough to use `bolt.Bucket.Stats().KeyN` but // we also need to retrieve the count of unread emails therefore we are // looping all messages in this mailbox by `bolt.Cursor` @@ -48,16 +48,19 @@ func (storeMailbox *Mailbox) txGetCounts(tx *bolt.Tx) (total, unread uint, err e total++ rawMsg := metaBucket.Get(apiID) if rawMsg == nil { - return 0, 0, ErrNoSuchAPIID + return 0, 0, 0, ErrNoSuchAPIID } // Do not unmarshal whole JSON to speed up the looping. // Instead, we assume it will contain JSON int field `Unread` // where `1` means true (i.e. message is unread) if bytes.Contains(rawMsg, []byte(`"Unread":1`)) { + if unseenSeqNum == 0 { + unseenSeqNum = total + } unread++ } } - return total, unread, err + return total, unread, unseenSeqNum, err } type mailboxCounts struct { diff --git a/internal/store/mailbox_message.go b/internal/store/mailbox_message.go index 55adfee0..79276484 100644 --- a/internal/store/mailbox_message.go +++ b/internal/store/mailbox_message.go @@ -376,7 +376,7 @@ func (storeMailbox *Mailbox) txDeleteMessage(tx *bolt.Tx, apiID string) error { } func (storeMailbox *Mailbox) txMailboxStatusUpdate(tx *bolt.Tx) error { - total, unread, err := storeMailbox.txGetCounts(tx) + total, unread, unreadSeqNum, err := storeMailbox.txGetCounts(tx) if err != nil { return errors.Wrap(err, "cannot get counts for mailbox status update") } @@ -385,6 +385,7 @@ func (storeMailbox *Mailbox) txMailboxStatusUpdate(tx *bolt.Tx) error { storeMailbox.labelName, total, unread, + unreadSeqNum, ) return nil } diff --git a/internal/store/user_sync.go b/internal/store/user_sync.go index fb58403b..646d525d 100644 --- a/internal/store/user_sync.go +++ b/internal/store/user_sync.go @@ -75,7 +75,7 @@ func (store *Store) isSynced(countsOnAPI []*pmapi.MessagesCount) (bool, error) { ) } - mboxTot, mboxUnread, err := mbox.GetCounts() + mboxTot, mboxUnread, _, err := mbox.GetCounts() if err != nil { errW := errors.Wrap(err, "cannot count messages") store.log. diff --git a/test/features/imap/mailbox/info.feature b/test/features/imap/mailbox/info.feature index b691fbdf..2fd0a8cd 100644 --- a/test/features/imap/mailbox/info.feature +++ b/test/features/imap/mailbox/info.feature @@ -10,6 +10,9 @@ Feature: IMAP get mailbox info Scenario: Mailbox info contains mailbox name When IMAP client gets info of "INBOX" Then IMAP response contains "2 EXISTS" - And IMAP response contains "UNSEEN 1" + # Messages are inserted in opposite way to keep increasing UID. + # Sequence numbers are then opposite than listed above. + # Unseen should have first unseen message. + And IMAP response contains "UNSEEN 2" And IMAP response contains "UIDNEXT 3" And IMAP response contains "UIDVALIDITY" diff --git a/test/store_checks_test.go b/test/store_checks_test.go index 63add19d..dfb4d94d 100644 --- a/test/store_checks_test.go +++ b/test/store_checks_test.go @@ -93,7 +93,7 @@ func mailboxForAddressOfUserHasNumberOfMessages(mailboxName, bddAddressID, bddUs start := time.Now() for { afterLimit := time.Since(start) > ctx.EventLoopTimeout() - total, _, _ := mailbox.GetCounts() + total, _, _, _ := mailbox.GetCounts() if total == uint(countOfMessages) { break }