-
Notifications
You must be signed in to change notification settings - Fork 2
/
user_address_info.go
158 lines (128 loc) · 4.89 KB
/
user_address_info.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
// Copyright (c) 2022 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 store
import (
"encoding/json"
"strings"
"github.com/ljanyst/peroxide/pkg/pmapi"
"github.com/pkg/errors"
bolt "go.etcd.io/bbolt"
)
// AddressInfo is the format of the data held in the addresses bucket in the store.
// It allows us to easily keep an address and its ID together and serialisation/deserialisation to []byte.
type AddressInfo struct {
Address, AddressID string
}
// GetAddressID returns the ID of the given address.
func (store *Store) GetAddressID(addr string) (id string, err error) {
addrs, err := store.GetAddressInfo()
if err != nil {
return
}
for _, addrInfo := range addrs {
if strings.EqualFold(addrInfo.Address, addr) {
id = addrInfo.AddressID
return
}
}
err = errors.New("no such address")
return
}
// GetAddressInfo returns information about all addresses owned by the user.
// The first element is the user's primary address and the rest (if present) are aliases.
// It tries to source the information from the store but if the store doesn't yet have it, it
// fetches it from the API and caches it for later.
func (store *Store) GetAddressInfo() (addrs []AddressInfo, err error) {
if addrs, err = store.getAddressInfoFromStore(); err == nil && len(addrs) > 0 {
return
}
// Store does not have address info yet, need to build it first from API.
addressList := store.client().Addresses()
if addressList == nil {
err = errors.New("addresses unavailable")
store.log.WithError(err).Error("Could not get user addresses from API")
return
}
if err = store.createOrUpdateAddressInfo(addressList); err != nil {
store.log.WithError(err).Warn("Could not update address IDs in store")
return
}
return store.getAddressInfoFromStore()
}
// getAddressIDsByAddressFromStore returns a map from address to addressID for each address owned by the user.
func (store *Store) getAddressInfoFromStore() (addrs []AddressInfo, err error) {
store.log.Debug("Retrieving address info from store")
tx := func(tx *bolt.Tx) (err error) {
c := tx.Bucket(addressInfoBucket).Cursor()
for index, addrInfoBytes := c.First(); index != nil; index, addrInfoBytes = c.Next() {
var addrInfo AddressInfo
if err = json.Unmarshal(addrInfoBytes, &addrInfo); err != nil {
store.log.WithError(err).Error("Could not unmarshal address and addressID")
return
}
addrs = append(addrs, addrInfo)
}
return
}
err = store.db.View(tx)
return
}
// createOrUpdateAddressInfo updates the store address/addressID bucket to match the given address list.
// The address list supplied is assumed to contain active emails in any order.
// It firstly (and stupidly) deletes the bucket of addresses and then fills it with up to date info.
// This is because a user might delete an address and we don't want old addresses lying around (and finding the
// specific ones to delete is likely not much more efficient than just rebuilding from scratch).
func (store *Store) createOrUpdateAddressInfo(addressList pmapi.AddressList) (err error) {
tx := func(tx *bolt.Tx) error {
if err := tx.DeleteBucket(addressInfoBucket); err != nil {
store.log.WithError(err).Error("Could not delete addressIDs bucket")
return err
}
if _, err := tx.CreateBucketIfNotExists(addressInfoBucket); err != nil {
store.log.WithError(err).Error("Could not recreate addressIDs bucket")
return err
}
addrsBucket := tx.Bucket(addressInfoBucket)
for index, address := range filterAddresses(addressList) {
ib := itob(uint32(index))
info, err := json.Marshal(AddressInfo{
Address: address.Email,
AddressID: address.ID,
})
if err != nil {
store.log.WithError(err).Error("Could not marshal address and addressID")
return err
}
if err := addrsBucket.Put(ib, info); err != nil {
store.log.WithError(err).Error("Could not put address and addressID into store")
return err
}
}
return nil
}
return store.db.Update(tx)
}
// filterAddresses filters out inactive addresses and ensures the original address is listed first.
func filterAddresses(addressList pmapi.AddressList) (filteredList pmapi.AddressList) {
for _, address := range addressList {
if !address.Receive {
continue
}
filteredList = append(filteredList, address)
}
return
}