mirror of
https://github.com/ProtonMail/proton-bridge.git
synced 2025-12-21 01:26:48 +00:00
We build too many walls and not enough bridges
This commit is contained in:
265
internal/store/mailbox.go
Normal file
265
internal/store/mailbox.go
Normal file
@ -0,0 +1,265 @@
|
||||
// Copyright (c) 2020 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
// ProtonMail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// ProtonMail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package store
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
||||
"github.com/sirupsen/logrus"
|
||||
bolt "go.etcd.io/bbolt"
|
||||
)
|
||||
|
||||
// Mailbox is mailbox for specific address and mailbox.
|
||||
type Mailbox struct {
|
||||
store *Store
|
||||
storeAddress *Address
|
||||
|
||||
labelID string
|
||||
labelPrefix string
|
||||
labelName string
|
||||
color string
|
||||
|
||||
log *logrus.Entry
|
||||
}
|
||||
|
||||
func newMailbox(storeAddress *Address, labelID, labelPrefix, labelName, color string) (mb *Mailbox, err error) {
|
||||
l := log.
|
||||
WithField("addrID", storeAddress.addressID).
|
||||
WithField("lblID", labelID)
|
||||
mb = &Mailbox{
|
||||
store: storeAddress.store,
|
||||
storeAddress: storeAddress,
|
||||
labelID: labelID,
|
||||
labelPrefix: labelPrefix,
|
||||
labelName: labelPrefix + labelName,
|
||||
color: color,
|
||||
log: l,
|
||||
}
|
||||
|
||||
if err = mb.store.db.Update(func(tx *bolt.Tx) error {
|
||||
return initMailboxBucket(tx, mb.getBucketName())
|
||||
}); err != nil {
|
||||
l.WithError(err).Error("Could not initialise mailbox buckets")
|
||||
}
|
||||
|
||||
syncDraftsIfNecssary(mb)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func syncDraftsIfNecssary(mb *Mailbox) { //nolint[funlen]
|
||||
// We didn't support drafts before v1.2.6 and therefore if we now created
|
||||
// Drafts mailbox we need to check whether counts match (drafts are synced).
|
||||
// If not, sync them from local metadata without need to do full resync,
|
||||
// Can be removed with 1.2.7 or later.
|
||||
if mb.labelID != pmapi.DraftLabel {
|
||||
return
|
||||
}
|
||||
|
||||
// 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.GetCounts()
|
||||
if err != nil || total != 0 {
|
||||
return
|
||||
}
|
||||
|
||||
counts, err := mb.store.getOnAPICounts()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
foundCounts := false
|
||||
doSync := false
|
||||
for _, count := range counts {
|
||||
if count.LabelID != pmapi.DraftLabel {
|
||||
continue
|
||||
}
|
||||
foundCounts = true
|
||||
log.WithField("total", total).WithField("total-api", count.TotalOnAPI).Debug("Drafts mailbox created: checking need for sync")
|
||||
if count.TotalOnAPI == total {
|
||||
continue
|
||||
}
|
||||
doSync = true
|
||||
break
|
||||
}
|
||||
|
||||
if !foundCounts {
|
||||
log.Debug("Drafts mailbox created: missing counts, refreshing")
|
||||
_ = mb.store.updateCountsFromServer()
|
||||
}
|
||||
|
||||
if !foundCounts || doSync {
|
||||
err := mb.store.db.Update(func(tx *bolt.Tx) error {
|
||||
return tx.Bucket(metadataBucket).ForEach(func(k, v []byte) error {
|
||||
msg := &pmapi.Message{}
|
||||
if err := json.Unmarshal(v, msg); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, msgLabelID := range msg.LabelIDs {
|
||||
if msgLabelID == pmapi.DraftLabel {
|
||||
log.WithField("id", msg.ID).Debug("Drafts mailbox created: syncing draft locally")
|
||||
_ = mb.txCreateOrUpdateMessages(tx, []*pmapi.Message{msg})
|
||||
break
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
})
|
||||
log.WithError(err).Info("Drafts mailbox created: synced localy")
|
||||
}
|
||||
}
|
||||
|
||||
func initMailboxBucket(tx *bolt.Tx, bucketName []byte) error {
|
||||
bucket, err := tx.Bucket(mailboxesBucket).CreateBucketIfNotExists(bucketName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := bucket.CreateBucketIfNotExists(imapIDsBucket); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := bucket.CreateBucketIfNotExists(apiIDsBucket); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// LabelID returns ID of mailbox.
|
||||
func (storeMailbox *Mailbox) LabelID() string {
|
||||
return storeMailbox.labelID
|
||||
}
|
||||
|
||||
// Name returns the name of mailbox.
|
||||
func (storeMailbox *Mailbox) Name() string {
|
||||
return storeMailbox.labelName
|
||||
}
|
||||
|
||||
// Color returns the color of mailbox.
|
||||
func (storeMailbox *Mailbox) Color() string {
|
||||
return storeMailbox.color
|
||||
}
|
||||
|
||||
// UIDValidity returns the current value of structure version.
|
||||
func (storeMailbox *Mailbox) UIDValidity() uint32 {
|
||||
return storeMailbox.store.getMailboxesVersion()
|
||||
}
|
||||
|
||||
// IsFolder returns whether the mailbox is a folder (has "Folders/" prefix).
|
||||
func (storeMailbox *Mailbox) IsFolder() bool {
|
||||
return storeMailbox.labelPrefix == UserFoldersPrefix
|
||||
}
|
||||
|
||||
// IsLabel returns whether the mailbox is a label (has "Labels/" prefix).
|
||||
func (storeMailbox *Mailbox) IsLabel() bool {
|
||||
return storeMailbox.labelPrefix == UserLabelsPrefix
|
||||
}
|
||||
|
||||
// IsSystem returns whether the mailbox is one of the specific system mailboxes (has no prefix).
|
||||
func (storeMailbox *Mailbox) IsSystem() bool {
|
||||
return storeMailbox.labelPrefix == ""
|
||||
}
|
||||
|
||||
// Rename updates the mailbox by calling an API.
|
||||
// Change has to be propagated to all the same mailboxes in all addresses.
|
||||
// The propagation is processed by the event loop.
|
||||
func (storeMailbox *Mailbox) Rename(newName string) error {
|
||||
if storeMailbox.IsSystem() {
|
||||
return fmt.Errorf("cannot rename system mailboxes")
|
||||
}
|
||||
|
||||
if storeMailbox.IsFolder() {
|
||||
if !strings.HasPrefix(newName, UserFoldersPrefix) {
|
||||
return fmt.Errorf("cannot rename folder to non-folder")
|
||||
}
|
||||
|
||||
newName = strings.TrimPrefix(newName, UserFoldersPrefix)
|
||||
}
|
||||
|
||||
if storeMailbox.IsLabel() {
|
||||
if !strings.HasPrefix(newName, UserLabelsPrefix) {
|
||||
return fmt.Errorf("cannot rename label to non-label")
|
||||
}
|
||||
|
||||
newName = strings.TrimPrefix(newName, UserLabelsPrefix)
|
||||
}
|
||||
|
||||
return storeMailbox.storeAddress.updateMailbox(storeMailbox.labelID, newName, storeMailbox.color)
|
||||
}
|
||||
|
||||
// Delete deletes the mailbox by calling an API.
|
||||
// Deletion has to be propagated to all the same mailboxes in all addresses.
|
||||
// The propagation is processed by the event loop.
|
||||
func (storeMailbox *Mailbox) Delete() error {
|
||||
return storeMailbox.storeAddress.deleteMailbox(storeMailbox.labelID)
|
||||
}
|
||||
|
||||
// GetDelimiter returns the path separator.
|
||||
func (storeMailbox *Mailbox) GetDelimiter() string {
|
||||
return PathDelimiter
|
||||
}
|
||||
|
||||
// deleteMailboxEvent deletes the mailbox bucket.
|
||||
// This is called from the event loop.
|
||||
func (storeMailbox *Mailbox) deleteMailboxEvent() error {
|
||||
return storeMailbox.db().Update(func(tx *bolt.Tx) error {
|
||||
return tx.Bucket(mailboxesBucket).DeleteBucket(storeMailbox.getBucketName())
|
||||
})
|
||||
}
|
||||
|
||||
// txGetIMAPIDsBucket returns the bucket mapping IMAP ID to API ID.
|
||||
func (storeMailbox *Mailbox) txGetIMAPIDsBucket(tx *bolt.Tx) *bolt.Bucket {
|
||||
return storeMailbox.txGetBucket(tx).Bucket(imapIDsBucket)
|
||||
}
|
||||
|
||||
// txGetAPIIDsBucket returns the bucket mapping API ID to IMAP ID.
|
||||
func (storeMailbox *Mailbox) txGetAPIIDsBucket(tx *bolt.Tx) *bolt.Bucket {
|
||||
return storeMailbox.txGetBucket(tx).Bucket(apiIDsBucket)
|
||||
}
|
||||
|
||||
// txGetBucket returns the bucket of mailbox containing mapping buckets.
|
||||
func (storeMailbox *Mailbox) txGetBucket(tx *bolt.Tx) *bolt.Bucket {
|
||||
return tx.Bucket(mailboxesBucket).Bucket(storeMailbox.getBucketName())
|
||||
}
|
||||
|
||||
func getMailboxBucketName(addressID, labelID string) []byte {
|
||||
return []byte(addressID + "-" + labelID)
|
||||
}
|
||||
|
||||
// getBucketName returns the name of mailbox bucket.
|
||||
func (storeMailbox *Mailbox) getBucketName() []byte {
|
||||
return getMailboxBucketName(storeMailbox.storeAddress.addressID, storeMailbox.labelID)
|
||||
}
|
||||
|
||||
// pollNow is a proxy for the store's eventloop's `pollNow()`.
|
||||
func (storeMailbox *Mailbox) pollNow() {
|
||||
storeMailbox.store.eventLoop.pollNow()
|
||||
}
|
||||
|
||||
// api is a proxy for the store's `PMAPIProvider`.
|
||||
func (storeMailbox *Mailbox) api() PMAPIProvider {
|
||||
return storeMailbox.store.api
|
||||
}
|
||||
|
||||
// update is a proxy for the store's db's `Update`.
|
||||
func (storeMailbox *Mailbox) db() *bolt.DB {
|
||||
return storeMailbox.store.db
|
||||
}
|
||||
Reference in New Issue
Block a user