mirror of
https://github.com/ProtonMail/proton-bridge.git
synced 2025-12-10 04:36:43 +00:00
212 lines
6.1 KiB
Go
212 lines
6.1 KiB
Go
// Copyright (c) 2025 Proton AG
|
|
//
|
|
// This file is part of Proton Mail Bridge.
|
|
//
|
|
// Proton Mail 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.
|
|
//
|
|
// Proton Mail 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 Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
package imapservice
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
|
|
"github.com/ProtonMail/gluon/imap"
|
|
"github.com/ProtonMail/go-proton-api"
|
|
"github.com/ProtonMail/proton-bridge/v3/internal/events"
|
|
"github.com/ProtonMail/proton-bridge/v3/internal/logging"
|
|
"github.com/bradenaw/juniper/xslices"
|
|
"github.com/sirupsen/logrus"
|
|
"golang.org/x/exp/maps"
|
|
)
|
|
|
|
func (s *Service) HandleLabelEvents(ctx context.Context, events []proton.LabelEvent) error {
|
|
s.log.Debug("handling label event")
|
|
|
|
for _, event := range events {
|
|
switch event.Action {
|
|
case proton.EventCreate:
|
|
if !WantLabel(event.Label) {
|
|
continue
|
|
}
|
|
|
|
updates, err := onLabelCreated(ctx, s, event)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to handle create label event: %w", err)
|
|
}
|
|
|
|
if err := waitOnIMAPUpdates(ctx, updates); err != nil {
|
|
return err
|
|
}
|
|
|
|
case proton.EventUpdateFlags, proton.EventUpdate:
|
|
if !WantLabel(event.Label) {
|
|
continue
|
|
}
|
|
|
|
updates, err := onLabelUpdated(ctx, s, event)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to handle update label event: %w", err)
|
|
}
|
|
|
|
if err := waitOnIMAPUpdates(ctx, updates); err != nil {
|
|
return err
|
|
}
|
|
|
|
case proton.EventDelete:
|
|
updates := onLabelDeleted(ctx, s, event)
|
|
|
|
if err := waitOnIMAPUpdates(ctx, updates); err != nil {
|
|
return fmt.Errorf("failed to handle delete label event in gluon: %w", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func onLabelCreated(ctx context.Context, s *Service, event proton.LabelEvent) ([]imap.Update, error) {
|
|
updates := []imap.Update{}
|
|
|
|
s.log.WithFields(logrus.Fields{
|
|
"labelID": event.ID,
|
|
"name": logging.Sensitive(event.Label.Name),
|
|
}).Info("Handling label created event")
|
|
|
|
wr := s.labels.Write()
|
|
defer wr.Close()
|
|
|
|
wr.SetLabel(event.Label.ID, event.Label, "onLabelCreated")
|
|
|
|
labelConflictResolver := s.labelConflictManager.NewConflictResolver(maps.Values(s.connectors))
|
|
conflictUpdatesGenerator, err := labelConflictResolver.ResolveConflict(ctx, event.Label, make(map[string]bool))
|
|
if err != nil {
|
|
return updates, err
|
|
}
|
|
|
|
for _, updateCh := range maps.Values(s.connectors) {
|
|
conflictUpdates := conflictUpdatesGenerator()
|
|
updateCh.publishUpdate(ctx, conflictUpdates...)
|
|
updates = append(updates, conflictUpdates...)
|
|
|
|
update := newMailboxCreatedUpdate(imap.MailboxID(event.ID), GetMailboxName(event.Label))
|
|
updateCh.publishUpdate(ctx, update)
|
|
updates = append(updates, update)
|
|
}
|
|
|
|
s.eventPublisher.PublishEvent(ctx, events.UserLabelCreated{
|
|
UserID: s.identityState.UserID(),
|
|
LabelID: event.Label.ID,
|
|
Name: event.Label.Name,
|
|
})
|
|
|
|
return updates, nil
|
|
}
|
|
|
|
func onLabelUpdated(ctx context.Context, s *Service, event proton.LabelEvent) ([]imap.Update, error) {
|
|
var updates []imap.Update
|
|
|
|
s.log.WithFields(logrus.Fields{
|
|
"labelID": event.ID,
|
|
"name": logging.Sensitive(event.Label.Name),
|
|
}).Info("Handling label updated event")
|
|
|
|
stack := []proton.Label{event.Label}
|
|
|
|
wr := s.labels.Write()
|
|
defer wr.Close()
|
|
|
|
for len(stack) > 0 {
|
|
label := stack[0]
|
|
stack = stack[1:]
|
|
|
|
// Only update the label if it exists; we don't want to create it as a client may have just deleted it.
|
|
if _, ok := wr.GetLabel(label.ID); ok {
|
|
wr.SetLabel(label.ID, event.Label, "onLabelUpdatedLabelEventID")
|
|
}
|
|
|
|
// API doesn't notify us that the path has changed. We need to fetch it again.
|
|
apiLabel, err := s.client.GetLabel(ctx, label.ID, label.Type)
|
|
if apiErr := new(proton.APIError); errors.As(err, &apiErr) && apiErr.Status == http.StatusUnprocessableEntity {
|
|
s.log.WithError(apiErr).Warn("Failed to get label: label does not exist")
|
|
continue
|
|
} else if err != nil {
|
|
return nil, fmt.Errorf("failed to get label %q: %w", label.ID, err)
|
|
}
|
|
|
|
// Update the label in the map.
|
|
wr.SetLabel(apiLabel.ID, apiLabel, "onLabelUpdatedApiID")
|
|
|
|
// Resolve potential conflicts
|
|
labelConflictResolver := s.labelConflictManager.NewConflictResolver(maps.Values(s.connectors))
|
|
conflictUpdatesGenerator, err := labelConflictResolver.ResolveConflict(ctx, event.Label, make(map[string]bool))
|
|
if err != nil {
|
|
return updates, err
|
|
}
|
|
|
|
// Notify the IMAP clients.
|
|
for _, updateCh := range maps.Values(s.connectors) {
|
|
conflictUpdates := conflictUpdatesGenerator()
|
|
updateCh.publishUpdate(ctx, conflictUpdates...)
|
|
updates = append(updates, conflictUpdates...)
|
|
|
|
update := imap.NewMailboxUpdated(
|
|
imap.MailboxID(apiLabel.ID),
|
|
GetMailboxName(apiLabel),
|
|
)
|
|
updateCh.publishUpdate(ctx, update)
|
|
updates = append(updates, update)
|
|
}
|
|
|
|
s.eventPublisher.PublishEvent(ctx, events.UserLabelUpdated{
|
|
UserID: s.identityState.UserID(),
|
|
LabelID: apiLabel.ID,
|
|
Name: apiLabel.Name,
|
|
})
|
|
|
|
children := xslices.Filter(wr.GetLabels(), func(other proton.Label) bool {
|
|
return other.ParentID == label.ID
|
|
})
|
|
|
|
stack = append(stack, children...)
|
|
}
|
|
|
|
return updates, nil
|
|
}
|
|
|
|
func onLabelDeleted(ctx context.Context, s *Service, event proton.LabelEvent) []imap.Update {
|
|
updates := make([]imap.Update, 0, len(s.connectors))
|
|
|
|
s.log.WithField("labelID", event.ID).Info("Handling label deleted event")
|
|
|
|
for _, updateCh := range maps.Values(s.connectors) {
|
|
update := imap.NewMailboxDeleted(imap.MailboxID(event.ID))
|
|
updateCh.publishUpdate(ctx, update)
|
|
updates = append(updates, update)
|
|
}
|
|
|
|
wr := s.labels.Write()
|
|
wr.Close()
|
|
|
|
wr.Delete(event.ID, "onLabelDeleted")
|
|
|
|
s.eventPublisher.PublishEvent(ctx, events.UserLabelDeleted{
|
|
UserID: s.identityState.UserID(),
|
|
LabelID: event.ID,
|
|
})
|
|
|
|
return updates
|
|
}
|