GODT-1468: Fix main windows status and add background context without retry.

This commit is contained in:
Jakub
2021-12-13 12:29:49 +01:00
parent 3bb9146d9f
commit d40fbda2ab
12 changed files with 101 additions and 112 deletions

View File

@ -36,12 +36,27 @@ Item {
if (root.usedFraction < .75) return root.colorScheme.signal_warning
return root.colorScheme.signal_danger
}
property real usedFraction: root.user && root.user.totalBytes ? Math.abs(root.user.usedBytes / root.user.totalBytes) : 0
property string totalSpace: root.spaceWithUnits(root.user ? root.user.totalBytes : 0)
property string usedSpace: root.spaceWithUnits(root.user ? root.user.usedBytes : 0)
property real usedFraction: root.user ? reasonableFracion(root.user.usedBytes, root.user.totalBytes) : 0
property string totalSpace: root.spaceWithUnits(root.user ? root.reasonableBytes(root.user.totalBytes) : 0)
property string usedSpace: root.spaceWithUnits(root.user ? root.reasonableBytes(root.user.usedBytes) : 0)
function reasonableFracion(used, total){
var usedSafe = root.reasonableBytes(used)
var totalSafe = root.reasonableBytes(total)
if (totalSafe == 0 || usedSafe == 0) return 0
if (totalSafe <= usedSafe) return 1
return usedSafe / totalSafe
}
function reasonableBytes(bytes){
var safeBytes = bytes+0
if (safeBytes != bytes) return 0
if (safeBytes < 0) return 0
return Math.ceil(safeBytes)
}
function spaceWithUnits(bytes){
if (bytes*1 !== bytes ) return "0 kB"
if (bytes*1 !== bytes || bytes == 0 ) return "0 kB"
var units = ['B',"kB", "MB", "GB", "TB"];
var i = parseInt(Math.floor(Math.log(bytes)/Math.log(1024)));

View File

@ -242,7 +242,7 @@ Window {
// add one user on start
var haveUserOnStart = false
var haveUserOnStart = true
if (haveUserOnStart) {
var newUserObject = root.userComponent.createObject(root)
newUserObject.username = "LerooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooyJenkins@protonmail.com"
@ -808,7 +808,7 @@ Window {
signal userDisconnected(string username)
signal apiCertIssue()
property bool showSplashScreen: true
property bool showSplashScreen: false
function login(username, password) {

View File

@ -20,6 +20,7 @@ import QtQuick.Layouts 1.12
import QtQuick.Controls 2.12
import Proton 4.0
import Notifications 1.0
Item {
id: root
@ -59,12 +60,16 @@ Item {
spacing: 0
Status {
colorScheme: leftBar.colorScheme
Layout.leftMargin: 16
Layout.topMargin: 24
Layout.bottomMargin: 17
Layout.alignment: Qt.AlignHCenter
colorScheme: leftBar.colorScheme
backend: root.backend
notifications: root.notifications
notificationWhitelist: Notifications.Group.Connection | Notifications.Group.ForceUpdate
}
// just a placeholder

View File

@ -37,10 +37,11 @@ QtObject {
signal askDeleteAccount(var user)
enum Group {
Connection = 1,
Update = 2,
Connection = 1,
Update = 2,
Configuration = 4,
API = 32,
ForceUpdate = 8,
API = 32,
// Special group for notifications that require dialog popup instead of banner
Dialogs = 64
@ -201,7 +202,7 @@ QtObject {
brief: qsTr("Bridge is outdated")
icon: "./icons/ic-exclamation-circle-filled.svg"
type: Notification.NotificationType.Danger
group: Notifications.Group.Update | Notifications.Group.Dialogs
group: Notifications.Group.Update | Notifications.Group.ForceUpdate | Notifications.Group.Dialogs
Connections {
target: root.backend

View File

@ -59,19 +59,19 @@ Item {
label.text = topmost.brief
switch (topmost.type) {
case Notification.NotificationType.Danger:
case Notification.NotificationType.Danger:
image.color = root.colorScheme.signal_danger
label.color = root.colorScheme.signal_danger
break;
case Notification.NotificationType.Warning:
case Notification.NotificationType.Warning:
image.color = root.colorScheme.signal_warning
label.color = root.colorScheme.signal_warning
break;
case Notification.NotificationType.Success:
case Notification.NotificationType.Success:
image.color = root.colorScheme.signal_success
label.color = root.colorScheme.signal_success
break;
case Notification.NotificationType.Info:
case Notification.NotificationType.Info:
image.color = root.colorScheme.signal_info
label.color = root.colorScheme.signal_info
break;
@ -85,8 +85,12 @@ Item {
ColorImage {
id: image
Layout.fillHeight: true
width: 16
height: 16
sourceSize.width: width
sourceSize.height: height
source: "./icons/ic-connected.svg"
color: root.colorScheme.signal_success
}
Label {
@ -100,88 +104,9 @@ Item {
horizontalAlignment: Text.AlignLeft
verticalAlignment: Text.AlignVCenter
text: qsTr("Connected")
color: root.colorScheme.signal_success
}
}
state: "Connected"
states: [
State {
name: "Connected"
PropertyChanges {
target: image
source: "./icons/ic-connected.svg"
color: ProtonStyle.currentStyle.signal_success
}
PropertyChanges {
target: label
text: qsTr("Connected")
color: ProtonStyle.currentStyle.signal_success
}
},
State {
name: "No connection"
PropertyChanges {
target: image
source: "./icons/ic-no-connection.svg"
color: ProtonStyle.currentStyle.signal_danger
}
PropertyChanges {
target: label
text: qsTr("No connection")
color: ProtonStyle.currentStyle.signal_danger
}
},
State {
name: "Outdated"
PropertyChanges {
target: image
source: "./icons/ic-exclamation-circle-filled.svg"
color: ProtonStyle.currentStyle.signal_danger
}
PropertyChanges {
target: label
text: qsTr("Bridge is outdated")
color: ProtonStyle.currentStyle.signal_danger
}
},
State {
name: "Account changed"
PropertyChanges {
target: image
source: "./icons/ic-exclamation-circle-filled.svg"
color: ProtonStyle.currentStyle.signal_danger
}
PropertyChanges {
target: label
text: qsTr("The address list for your account has changed")
color: ProtonStyle.currentStyle.signal_danger
}
},
State {
name: "Auto update failed"
PropertyChanges {
target: image
source: "./icons/ic-info-circle-filled.svg"
color: ProtonStyle.currentStyle.signal_info
}
PropertyChanges {
target: label
text: qsTr("Bridge couldnt update automatically")
color: ProtonStyle.currentStyle.signal_info
}
},
State {
name: "Update ready"
PropertyChanges {
target: image
source: "./icons/ic-info-circle-filled.svg"
color: ProtonStyle.currentStyle.signal_info
}
PropertyChanges {
target: label
text: qsTr("Bridge update is ready")
color: ProtonStyle.currentStyle.signal_info
}
}
]
}

View File

@ -81,7 +81,7 @@ func (loop *eventLoop) client() pmapi.Client {
func (loop *eventLoop) setFirstEventID() (err error) {
loop.log.Info("Setting first event ID")
event, err := loop.client().GetEvent(context.Background(), "")
event, err := loop.client().GetEvent(pmapi.ContextWithoutRetry(context.Background()), "")
if err != nil {
loop.log.WithError(err).Error("Could not get latest event ID")
return
@ -269,7 +269,7 @@ func (loop *eventLoop) processNextEvent() (more bool, err error) { // nolint[fun
loop.pollCounter++
var event *pmapi.Event
if event, err = loop.client().GetEvent(context.Background(), loop.currentEventID); err != nil {
if event, err = loop.client().GetEvent(pmapi.ContextWithoutRetry(context.Background()), loop.currentEventID); err != nil {
return false, errors.Wrap(err, "failed to get event")
}

View File

@ -312,7 +312,7 @@ func (store *Store) client() pmapi.Client {
// initCounts initialises the counts for each label. It tries to use the API first to fetch the labels but if
// the API is unavailable for whatever reason it tries to fetch the labels locally.
func (store *Store) initCounts() (labels []*pmapi.Label, err error) {
if labels, err = store.client().ListLabels(context.Background()); err != nil {
if labels, err = store.client().ListLabels(pmapi.ContextWithoutRetry(context.Background())); err != nil {
store.log.WithError(err).Warn("Could not list API labels. Trying with local labels.")
if labels, err = store.getLabelsFromLocalStorage(); err != nil {
store.log.WithError(err).Error("Cannot list local labels")

View File

@ -223,7 +223,7 @@ func (u *User) UpdateSpace(apiUser *pmapi.User) {
// values from client.CurrentUser()
if apiUser == nil {
var err error
apiUser, err = u.client.GetUser(context.Background())
apiUser, err = u.client.GetUser(pmapi.ContextWithoutRetry(context.Background()))
if err != nil {
u.log.WithError(err).Warning("Cannot update user space")
return

View File

@ -34,6 +34,9 @@ type manager struct {
locker sync.Locker
connectionObservers []ConnectionObserver
proxyDialer *ProxyTLSDialer
pingMutex *sync.RWMutex
isPinging bool
}
func New(cfg Config) Manager {
@ -42,9 +45,11 @@ func New(cfg Config) Manager {
func newManager(cfg Config) *manager {
m := &manager{
cfg: cfg,
rc: resty.New().EnableTrace(),
locker: &sync.Mutex{},
cfg: cfg,
rc: resty.New().EnableTrace(),
locker: &sync.Mutex{},
pingMutex: &sync.RWMutex{},
isPinging: false,
}
proxyDialer, transport := newProxyDialerAndTransport(cfg)
@ -75,7 +80,7 @@ func newManager(cfg Config) *manager {
m.rc.SetRetryCount(30)
m.rc.SetRetryMaxWaitTime(time.Minute)
m.rc.SetRetryAfter(catchRetryAfter)
m.rc.AddRetryCondition(shouldRetry)
m.rc.AddRetryCondition(m.shouldRetry)
return m
}

View File

@ -21,6 +21,7 @@ import (
"io/ioutil"
"github.com/ProtonMail/gopenpgp/v2/crypto"
"golang.org/x/net/context"
)
// DownloadAndVerify downloads a file and its signature from the given locations `file` and `sig`.
@ -50,7 +51,7 @@ func (m *manager) DownloadAndVerify(kr *crypto.KeyRing, url, sig string) ([]byte
}
func (m *manager) fetchFile(url string) ([]byte, error) {
res, err := m.rc.R().SetDoNotParseResponse(true).Get(url)
res, err := m.r(ContextWithoutRetry(context.Background())).SetDoNotParseResponse(true).Get(url)
if err != nil {
return nil, err
}

View File

@ -30,20 +30,47 @@ var (
)
func (m *manager) pingUntilSuccess() {
if m.isPingOngoing() {
logrus.Debug("Ping already ongoing")
return
}
m.pingingStarted()
defer m.pingingStopped()
attempt := 0
for {
err := m.testPing(context.Background())
ctx := ContextWithoutRetry(context.Background())
err := m.testPing(ctx)
if err == nil {
return
}
waitTime := getRetryConnectionSleep(attempt)
attempt++
logrus.WithError(err).WithField("attempt", attempt).WithField("wait", waitTime).Debug("Connection not available")
logrus.WithError(err).WithField("attempt", attempt).WithField("wait", waitTime).Debug("Connection (still) not available")
time.Sleep(waitTime)
}
}
func (m *manager) isPingOngoing() bool {
m.pingMutex.RLock()
defer m.pingMutex.RUnlock()
return m.isPinging
}
func (m *manager) pingingStarted() {
m.pingMutex.Lock()
defer m.pingMutex.Unlock()
m.isPinging = true
}
func (m *manager) pingingStopped() {
m.pingMutex.Lock()
defer m.pingMutex.Unlock()
m.isPinging = false
}
func getRetryConnectionSleep(idx int) time.Duration {
if idx >= len(retryConnectionSleeps) {
idx = len(retryConnectionSleeps) - 1

View File

@ -118,11 +118,21 @@ func catchRetryAfter(_ *resty.Client, res *resty.Response) (time.Duration, error
return 0, nil
}
func shouldRetry(res *resty.Response, err error) bool {
func (m *manager) shouldRetry(res *resty.Response, err error) bool {
if isRetryDisabled(res.Request.Context()) {
return false
}
return isTooManyRequest(res) || isNoResponse(res, err)
if isTooManyRequest(res) {
return true
}
if isNoResponse(res, err) {
// Even if the context of request allows to retry we should check
// whether the server is reachable or not. In some cases the we can
// keep retrying but also report that connection is lost.
go m.pingUntilSuccess()
return true
}
return false
}
func isTooManyRequest(res *resty.Response) bool {