You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
169 lines
4.5 KiB
169 lines
4.5 KiB
// Copyright (c) 2021 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 transfer |
|
|
|
import ( |
|
"context" |
|
"sort" |
|
|
|
"github.com/ProtonMail/gopenpgp/v2/crypto" |
|
"github.com/ProtonMail/proton-bridge/pkg/message" |
|
"github.com/ProtonMail/proton-bridge/pkg/pmapi" |
|
"github.com/pkg/errors" |
|
) |
|
|
|
const ( |
|
fetchWorkers = 20 // In how many workers to fetch message (group list on IMAP). |
|
attachWorkers = 5 // In how many workers to fetch attachments (for one message). |
|
buildWorkers = 20 // In how many workers to build messages. |
|
) |
|
|
|
// PMAPIProvider implements import and export to/from ProtonMail server. |
|
type PMAPIProvider struct { |
|
client pmapi.Client |
|
userID string |
|
addressID string |
|
keyRing *crypto.KeyRing |
|
builder *message.Builder |
|
|
|
nextImportRequests map[string]*pmapi.ImportMsgReq // Key is msg transfer ID. |
|
nextImportRequestsSize int |
|
|
|
timeIt *timeIt |
|
|
|
connection bool |
|
} |
|
|
|
// NewPMAPIProvider returns new PMAPIProvider. |
|
func NewPMAPIProvider(client pmapi.Client, userID, addressID string) (*PMAPIProvider, error) { |
|
provider := &PMAPIProvider{ |
|
client: client, |
|
userID: userID, |
|
addressID: addressID, |
|
builder: message.NewBuilder(fetchWorkers, attachWorkers, buildWorkers), |
|
|
|
nextImportRequests: map[string]*pmapi.ImportMsgReq{}, |
|
nextImportRequestsSize: 0, |
|
|
|
timeIt: newTimeIt("pmapi"), |
|
} |
|
|
|
if addressID != "" { |
|
keyRing, err := client.KeyRingForAddressID(addressID) |
|
if err != nil { |
|
return nil, errors.Wrap(err, "failed to get key ring") |
|
} |
|
provider.keyRing = keyRing |
|
} |
|
|
|
return provider, nil |
|
} |
|
|
|
// ID returns identifier of current setup of PMAPI provider. |
|
// Identification is unique per user. |
|
func (p *PMAPIProvider) ID() string { |
|
return p.userID |
|
} |
|
|
|
// Mailboxes returns all available labels in ProtonMail account. |
|
func (p *PMAPIProvider) Mailboxes(includeEmpty, includeAllMail bool) ([]Mailbox, error) { |
|
labels, err := p.client.ListLabels(context.Background()) |
|
if err != nil { |
|
return nil, err |
|
} |
|
sortedLabels := byFoldersLabels(labels) |
|
sort.Sort(sortedLabels) |
|
|
|
emptyLabelsMap := map[string]bool{} |
|
if !includeEmpty { |
|
messagesCounts, err := p.client.CountMessages(context.Background(), p.addressID) |
|
if err != nil { |
|
return nil, err |
|
} |
|
for _, messagesCount := range messagesCounts { |
|
if messagesCount.Total == 0 { |
|
emptyLabelsMap[messagesCount.LabelID] = true |
|
} |
|
} |
|
} |
|
|
|
mailboxes := []Mailbox{} |
|
for _, mailbox := range getSystemMailboxes(includeAllMail) { |
|
if !includeEmpty && emptyLabelsMap[mailbox.ID] { |
|
continue |
|
} |
|
|
|
mailboxes = append(mailboxes, mailbox) |
|
} |
|
for _, label := range sortedLabels { |
|
if !includeEmpty && emptyLabelsMap[label.ID] { |
|
continue |
|
} |
|
|
|
mailboxes = append(mailboxes, Mailbox{ |
|
ID: label.ID, |
|
Name: label.Name, |
|
Color: label.Color, |
|
IsExclusive: bool(label.Exclusive), |
|
}) |
|
} |
|
return mailboxes, nil |
|
} |
|
|
|
func getSystemMailboxes(includeAllMail bool) []Mailbox { |
|
mailboxes := []Mailbox{ |
|
{ID: pmapi.InboxLabel, Name: "Inbox", IsExclusive: true}, |
|
{ID: pmapi.DraftLabel, Name: "Drafts", IsExclusive: true}, |
|
{ID: pmapi.SentLabel, Name: "Sent", IsExclusive: true}, |
|
{ID: pmapi.StarredLabel, Name: "Starred", IsExclusive: true}, |
|
{ID: pmapi.ArchiveLabel, Name: "Archive", IsExclusive: true}, |
|
{ID: pmapi.SpamLabel, Name: "Spam", IsExclusive: true}, |
|
{ID: pmapi.TrashLabel, Name: "Trash", IsExclusive: true}, |
|
} |
|
|
|
if includeAllMail { |
|
mailboxes = append(mailboxes, Mailbox{ |
|
ID: pmapi.AllMailLabel, |
|
Name: "All Mail", |
|
IsExclusive: true, |
|
}) |
|
} |
|
|
|
return mailboxes |
|
} |
|
|
|
type byFoldersLabels []*pmapi.Label |
|
|
|
func (l byFoldersLabels) Len() int { |
|
return len(l) |
|
} |
|
|
|
func (l byFoldersLabels) Swap(i, j int) { |
|
l[i], l[j] = l[j], l[i] |
|
} |
|
|
|
// Less sorts first folders, then labels, by user order. |
|
func (l byFoldersLabels) Less(i, j int) bool { |
|
if l[i].Exclusive && !l[j].Exclusive { |
|
return true |
|
} |
|
if !l[i].Exclusive && l[j].Exclusive { |
|
return false |
|
} |
|
return l[i].Order < l[j].Order |
|
}
|
|
|