mirror of
https://github.com/ProtonMail/proton-bridge.git
synced 2025-12-18 08:06:59 +00:00
GODT-1499: Remove message from DB once is not on server any more
This commit is contained in:
@ -23,6 +23,7 @@ import (
|
|||||||
"github.com/ProtonMail/gopenpgp/v2/crypto"
|
"github.com/ProtonMail/gopenpgp/v2/crypto"
|
||||||
"github.com/ProtonMail/proton-bridge/internal/store/cache"
|
"github.com/ProtonMail/proton-bridge/internal/store/cache"
|
||||||
"github.com/ProtonMail/proton-bridge/pkg/message"
|
"github.com/ProtonMail/proton-bridge/pkg/message"
|
||||||
|
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
||||||
bolt "go.etcd.io/bbolt"
|
bolt "go.etcd.io/bbolt"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -126,6 +127,7 @@ func (store *Store) getCachedMessage(messageID string) ([]byte, error) {
|
|||||||
|
|
||||||
literal, err := job.GetResult()
|
literal, err := job.GetResult()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
store.checkAndRemoveDeletedMessage(err, messageID)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -184,8 +186,21 @@ func (store *Store) BuildAndCacheMessage(ctx context.Context, messageID string)
|
|||||||
|
|
||||||
literal, err := job.GetResult()
|
literal, err := job.GetResult()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
store.checkAndRemoveDeletedMessage(err, messageID)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return store.cache.Set(store.user.ID(), messageID, literal)
|
return store.cache.Set(store.user.ID(), messageID, literal)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (store *Store) checkAndRemoveDeletedMessage(err error, msgID string) {
|
||||||
|
if _, ok := err.(pmapi.ErrUnprocessableEntity); !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
l := store.log.WithError(err).WithField("msgID", msgID)
|
||||||
|
l.Warn("Deleting message which was not found on API")
|
||||||
|
|
||||||
|
if deleteErr := store.deleteMessageEvent(msgID); deleteErr != nil {
|
||||||
|
l.WithField("deleteErr", deleteErr).Error("Failed to delete non-existed API message from DB")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -32,9 +32,9 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type ErrUnprocessableEntity struct {
|
type ErrUnprocessableEntity struct {
|
||||||
originalError error
|
OriginalError error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (err ErrUnprocessableEntity) Error() string {
|
func (err ErrUnprocessableEntity) Error() string {
|
||||||
return err.originalError.Error()
|
return err.OriginalError.Error()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -45,6 +45,7 @@ type PMAPIController interface {
|
|||||||
GetCalls(method, path string) [][]byte
|
GetCalls(method, path string) [][]byte
|
||||||
LockEvents(username string)
|
LockEvents(username string)
|
||||||
UnlockEvents(username string)
|
UnlockEvents(username string)
|
||||||
|
RemoveUserMessageWithoutEvent(username, messageID string) error
|
||||||
}
|
}
|
||||||
|
|
||||||
func newPMAPIController(listener listener.Listener) (PMAPIController, pmapi.Manager) {
|
func newPMAPIController(listener listener.Listener) (PMAPIController, pmapi.Manager) {
|
||||||
|
|||||||
@ -234,3 +234,19 @@ func (ctl *Controller) LockEvents(string) {}
|
|||||||
|
|
||||||
// UnlockEvents doesn't needs to be implemented for fakeAPI.
|
// UnlockEvents doesn't needs to be implemented for fakeAPI.
|
||||||
func (ctl *Controller) UnlockEvents(string) {}
|
func (ctl *Controller) UnlockEvents(string) {}
|
||||||
|
|
||||||
|
func (ctl *Controller) RemoveUserMessageWithoutEvent(username string, messageID string) error {
|
||||||
|
msgs, ok := ctl.messagesByUsername[username]
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, message := range msgs {
|
||||||
|
if message.ID == messageID {
|
||||||
|
ctl.messagesByUsername[username] = append(msgs[:i], msgs[i+1:]...)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors.New("message not found")
|
||||||
|
}
|
||||||
|
|||||||
@ -37,7 +37,7 @@ func (api *FakePMAPI) GetMessage(_ context.Context, apiID string) (*pmapi.Messag
|
|||||||
if msg := api.getMessage(apiID); msg != nil {
|
if msg := api.getMessage(apiID); msg != nil {
|
||||||
return msg, nil
|
return msg, nil
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("message %s not found", apiID)
|
return nil, pmapi.ErrUnprocessableEntity{OriginalError: fmt.Errorf("message %s not found", apiID)}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListMessages does not implement following filters:
|
// ListMessages does not implement following filters:
|
||||||
|
|||||||
@ -177,3 +177,14 @@ Feature: IMAP fetch messages
|
|||||||
# We had bug to incorectly set empty date, so let's make sure
|
# We had bug to incorectly set empty date, so let's make sure
|
||||||
# there is no reference anywhere in the response.
|
# there is no reference anywhere in the response.
|
||||||
And IMAP response does not contain "\nDate: Thu, 01 Jan 1970"
|
And IMAP response does not contain "\nDate: Thu, 01 Jan 1970"
|
||||||
|
|
||||||
|
Scenario: Fetch of message which was deleted without event processed
|
||||||
|
Given there are 10 messages in mailbox "INBOX" for "user"
|
||||||
|
And message "5" was deleted forever without event processed for "user"
|
||||||
|
And there is IMAP client logged in as "user"
|
||||||
|
And there is IMAP client selected in "INBOX"
|
||||||
|
When IMAP client fetches bodies "1:*"
|
||||||
|
Then IMAP response is "NO"
|
||||||
|
When IMAP client fetches bodies "1:*"
|
||||||
|
Then IMAP response is "OK"
|
||||||
|
And IMAP response has 9 messages
|
||||||
|
|||||||
@ -104,3 +104,14 @@ func (ctl *Controller) GetMessages(username, labelID string) ([]*pmapi.Message,
|
|||||||
|
|
||||||
return messages, nil
|
return messages, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ctl *Controller) RemoveUserMessageWithoutEvent(username string, messageID string) error {
|
||||||
|
client, err := getPersistentClient(username)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
addMessageIDToSkipEventOnceDeleted(messageID)
|
||||||
|
|
||||||
|
return client.DeleteMessages(context.Background(), []string{messageID})
|
||||||
|
}
|
||||||
|
|||||||
@ -48,7 +48,8 @@ var persistentClients = struct {
|
|||||||
byName map[string]clientAuthGetter
|
byName map[string]clientAuthGetter
|
||||||
saltByName map[string]string
|
saltByName map[string]string
|
||||||
|
|
||||||
eventsPaused sync.WaitGroup
|
eventsPaused sync.WaitGroup
|
||||||
|
skipDeletedMessageID map[string]struct{}
|
||||||
}{}
|
}{}
|
||||||
|
|
||||||
type persistentClient struct {
|
type persistentClient struct {
|
||||||
@ -79,7 +80,40 @@ func (pc *persistentClient) GetEvent(ctx context.Context, eventID string) (*pmap
|
|||||||
if !ok {
|
if !ok {
|
||||||
return nil, errors.New("cannot convert to normal client")
|
return nil, errors.New("cannot convert to normal client")
|
||||||
}
|
}
|
||||||
return normalClient.GetEvent(ctx, eventID)
|
|
||||||
|
event, err := normalClient.GetEvent(ctx, eventID)
|
||||||
|
if err != nil {
|
||||||
|
return event, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return skipDeletedMessageIDs(event), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func addMessageIDToSkipEventOnceDeleted(msgID string) {
|
||||||
|
if persistentClients.skipDeletedMessageID == nil {
|
||||||
|
persistentClients.skipDeletedMessageID = map[string]struct{}{}
|
||||||
|
}
|
||||||
|
persistentClients.skipDeletedMessageID[msgID] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func skipDeletedMessageIDs(event *pmapi.Event) *pmapi.Event {
|
||||||
|
if len(event.Messages) == 0 {
|
||||||
|
return event
|
||||||
|
}
|
||||||
|
|
||||||
|
n := 0
|
||||||
|
for i, m := range event.Messages {
|
||||||
|
if _, ok := persistentClients.skipDeletedMessageID[m.ID]; ok && m.Action == pmapi.EventDelete {
|
||||||
|
delete(persistentClients.skipDeletedMessageID, m.ID)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
event.Messages[i] = m
|
||||||
|
n++
|
||||||
|
}
|
||||||
|
event.Messages = event.Messages[:n]
|
||||||
|
|
||||||
|
return event
|
||||||
}
|
}
|
||||||
|
|
||||||
func SetupPersistentClients() {
|
func SetupPersistentClients() {
|
||||||
|
|||||||
@ -38,6 +38,7 @@ func StoreSetupFeatureContext(s *godog.ScenarioContext) {
|
|||||||
s.Step(`^there are messages for "([^"]*)" as follows$`, thereAreSomeMessagesForUserAsFollows)
|
s.Step(`^there are messages for "([^"]*)" as follows$`, thereAreSomeMessagesForUserAsFollows)
|
||||||
s.Step(`^there are (\d+) messages in mailbox(?:es)? "([^"]*)" for address "([^"]*)" of "([^"]*)"$`, thereAreSomeMessagesInMailboxesForAddressOfUser)
|
s.Step(`^there are (\d+) messages in mailbox(?:es)? "([^"]*)" for address "([^"]*)" of "([^"]*)"$`, thereAreSomeMessagesInMailboxesForAddressOfUser)
|
||||||
s.Step(`^wait for Sphinx to create duplication indices$`, waitForSphinx)
|
s.Step(`^wait for Sphinx to create duplication indices$`, waitForSphinx)
|
||||||
|
s.Step(`^message(?:s)? "([^"]*)" (?:was|were) deleted forever without event processed for "([^"]*)"$`, messageWasDeletedWithoutEvent)
|
||||||
}
|
}
|
||||||
|
|
||||||
func thereIsUserWithMailboxes(bddUserID string, mailboxes *godog.Table) error {
|
func thereIsUserWithMailboxes(bddUserID string, mailboxes *godog.Table) error {
|
||||||
@ -319,3 +320,16 @@ func waitForSphinx() error {
|
|||||||
time.Sleep(15 * time.Second)
|
time.Sleep(15 * time.Second)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func messageWasDeletedWithoutEvent(bddMessageID, bddUserID string) error {
|
||||||
|
account := ctx.GetTestAccount(bddUserID)
|
||||||
|
if account == nil {
|
||||||
|
return godog.ErrPending
|
||||||
|
}
|
||||||
|
apiID, err := ctx.GetAPIMessageID(account.Username(), bddMessageID)
|
||||||
|
if err != nil {
|
||||||
|
return internalError(err, "getting BDD message ID %s", bddMessageID)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ctx.GetPMAPIController().RemoveUserMessageWithoutEvent(account.Username(), apiID)
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user