-
Notifications
You must be signed in to change notification settings - Fork 27
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(GODT-2582): Deduplication of messages in Recovery Mailbox
When we attempt to recover avoid creating duplicates of existing recovered messages. We now keep track of the literal hash of all the messages in the recovered messages folder and if we see we are adding a known literal, we ignore the new version.
- Loading branch information
1 parent
3e8cc8b
commit c0ba09f
Showing
11 changed files
with
165 additions
and
22 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
package imap | ||
|
||
// ShortID return a string containing a short version of the given ID. Use only for debug display. | ||
func ShortID(id string) string { | ||
const l = 12 | ||
|
||
if len(id) < l { | ||
return id | ||
} | ||
|
||
return id[0:l] + "..." | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
package utils | ||
|
||
import ( | ||
"crypto/sha256" | ||
"encoding/hex" | ||
"github.com/ProtonMail/gluon/imap" | ||
"sync" | ||
) | ||
|
||
// MessageHashesMap tracks the hashes for a literal and it's associated internal IMAP ID. | ||
type MessageHashesMap struct { | ||
lock sync.Mutex | ||
idToHash map[imap.InternalMessageID]string | ||
hashes map[string]struct{} | ||
} | ||
|
||
func NewMessageHashesMap() *MessageHashesMap { | ||
return &MessageHashesMap{ | ||
idToHash: make(map[imap.InternalMessageID]string), | ||
hashes: make(map[string]struct{}), | ||
} | ||
} | ||
|
||
// Insert inserts the hash of the current message literal into the map and return true if an existing value was already | ||
// present. | ||
func (m *MessageHashesMap) Insert(id imap.InternalMessageID, literal []byte) (bool, error) { | ||
hash := sha256.New() | ||
|
||
if _, err := hash.Write(literal); err != nil { | ||
return false, err | ||
} | ||
|
||
literalHash := hash.Sum(nil) | ||
literalHashStr := hex.EncodeToString(literalHash) | ||
|
||
m.lock.Lock() | ||
defer m.lock.Unlock() | ||
|
||
if _, ok := m.hashes[literalHashStr]; ok { | ||
return true, nil | ||
} | ||
|
||
m.idToHash[id] = literalHashStr | ||
m.hashes[literalHashStr] = struct{}{} | ||
|
||
return false, nil | ||
} | ||
|
||
// Erase removes the info associated with a given id. | ||
func (m *MessageHashesMap) Erase(ids ...imap.InternalMessageID) { | ||
m.lock.Lock() | ||
defer m.lock.Unlock() | ||
|
||
for _, id := range ids { | ||
if v, ok := m.idToHash[id]; ok { | ||
delete(m.hashes, v) | ||
} | ||
|
||
delete(m.idToHash, id) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -200,6 +200,44 @@ func TestFailedAppendEndsInRecovery(t *testing.T) { | |
}) | ||
} | ||
|
||
func TestFailedAppendAreDedupedInRecoveryMailbox(t *testing.T) { | ||
runOneToOneTestClientWithAuth(t, defaultServerOptions(t, withConnectorBuilder(&failAppendLabelConnectorBuilder{})), func(client *client.Client, s *testSession) { | ||
{ | ||
status, err := client.Status(ids.GluonRecoveryMailboxName, []goimap.StatusItem{goimap.StatusMessages}) | ||
require.NoError(t, err) | ||
require.Equal(t, uint32(0), status.Messages) | ||
} | ||
|
||
status, err := client.Select("INBOX", false) | ||
require.NoError(t, err) | ||
require.Equal(t, uint32(0), status.Messages) | ||
require.Error(t, doAppendWithClient(client, "INBOX", "To: [email protected]", time.Now())) | ||
require.Error(t, doAppendWithClient(client, "INBOX", "To: [email protected]", time.Now())) | ||
require.Error(t, doAppendWithClient(client, "INBOX", "To: [email protected]", time.Now())) | ||
|
||
{ | ||
status, err := client.Status(ids.GluonRecoveryMailboxName, []goimap.StatusItem{goimap.StatusMessages}) | ||
require.NoError(t, err) | ||
require.Equal(t, uint32(2), status.Messages) | ||
} | ||
{ | ||
status, err := client.Status("INBOX", []goimap.StatusItem{goimap.StatusMessages}) | ||
require.NoError(t, err) | ||
require.Equal(t, uint32(0), status.Messages) | ||
} | ||
|
||
{ | ||
_, err := client.Select(ids.GluonRecoveryMailboxName, false) | ||
require.NoError(t, err) | ||
// Check that no custom headers are appended to the message. | ||
newFetchCommand(t, client).withItems("BODY[]").fetch("1").forSeqNum(1, func(builder *validatorBuilder) { | ||
builder.ignoreFlags() | ||
builder.wantSection("BODY[]", "To: [email protected]") | ||
}).checkAndRequireMessageCount(1) | ||
} | ||
}) | ||
} | ||
|
||
func TestRecoveryMBoxCanBeCopiedOutOfDedup(t *testing.T) { | ||
runOneToOneTestClientWithAuth(t, defaultServerOptions(t, withConnectorBuilder(&recoveryDedupConnectorConnectorBuilder{})), func(client *client.Client, s *testSession) { | ||
// Insert first message, fails. | ||
|