feat(GODT-2709): Init Configuration status.

This commit is contained in:
Romain LE JEUNE
2023-06-27 08:42:47 +02:00
committed by Romain Le Jeune
parent 4e080b59d3
commit ff11d20d9c
10 changed files with 233 additions and 1 deletions

View File

@ -28,6 +28,7 @@ type Locator interface {
ProvideLogsPath() (string, error) ProvideLogsPath() (string, error)
ProvideGluonCachePath() (string, error) ProvideGluonCachePath() (string, error)
ProvideGluonDataPath() (string, error) ProvideGluonDataPath() (string, error)
ProvideStatsPath() (string, error)
GetLicenseFilePath() string GetLicenseFilePath() string
GetDependencyLicensesLink() string GetDependencyLicensesLink() string
Clear(...string) error Clear(...string) error

View File

@ -519,6 +519,11 @@ func (bridge *Bridge) addUserWithVault(
apiUser proton.User, apiUser proton.User,
vault *vault.User, vault *vault.User,
) error { ) error {
statsPath, err := bridge.locator.ProvideStatsPath()
if err != nil {
return fmt.Errorf("failed to get Statistics directory: %w", err)
}
user, err := user.New( user, err := user.New(
ctx, ctx,
vault, vault,
@ -528,6 +533,7 @@ func (bridge *Bridge) addUserWithVault(
bridge.panicHandler, bridge.panicHandler,
bridge.vault.GetShowAllMail(), bridge.vault.GetShowAllMail(),
bridge.vault.GetMaxSyncMemory(), bridge.vault.GetMaxSyncMemory(),
statsPath,
) )
if err != nil { if err != nil {
return fmt.Errorf("failed to create user: %w", err) return fmt.Errorf("failed to create user: %w", err)

View File

@ -188,6 +188,16 @@ func (l *Locations) ProvideUpdatesPath() (string, error) {
return l.getUpdatesPath(), nil return l.getUpdatesPath(), nil
} }
// ProvideStatsPath returns a location for statistics files (e.g. ~/.local/share/<company>/<app>/stats).
// It creates it if it doesn't already exist.
func (l *Locations) ProvideStatsPath() (string, error) {
if err := os.MkdirAll(l.getStatsPath(), 0o700); err != nil {
return "", err
}
return l.getStatsPath(), nil
}
func (l *Locations) getGluonCachePath() string { func (l *Locations) getGluonCachePath() string {
return filepath.Join(l.userData, "gluon") return filepath.Join(l.userData, "gluon")
} }
@ -216,6 +226,10 @@ func (l *Locations) getUpdatesPath() string {
return filepath.Join(l.userData, "updates") return filepath.Join(l.userData, "updates")
} }
func (l *Locations) getStatsPath() string {
return filepath.Join(l.userData, "stats")
}
// Clear removes everything except the lock and update files. // Clear removes everything except the lock and update files.
func (l *Locations) Clear(except ...string) error { func (l *Locations) Clear(except ...string) error {
return files.Remove( return files.Remove(

View File

@ -0,0 +1,18 @@
// Copyright (c) 2023 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 telemetry

View File

@ -0,0 +1,18 @@
// Copyright (c) 2023 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 telemetry

View File

@ -0,0 +1,18 @@
// Copyright (c) 2023 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 telemetry

View File

@ -0,0 +1,18 @@
// Copyright (c) 2023 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 telemetry

View File

@ -0,0 +1,127 @@
// Copyright (c) 2023 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 user
import (
"encoding/json"
"os"
"time"
)
type ConfigurationStatusData struct {
PendingSince time.Time `json:"auto_update"`
LastProgress time.Time `json:"last_progress"`
Autoconf string `json:"auto_conf"`
ClickedLink uint64 `json:"clicked_link"`
ReportSent bool `json:"report_sent"`
ReportClick bool `json:"report_click"`
FailureDetails string `json:"failure_details"`
}
type ConfigurationStatus struct {
FilePath string
Data ConfigurationStatusData
}
func LoadConfigurationStatus(filepath string) (*ConfigurationStatus, error) {
status := ConfigurationStatus{
FilePath: filepath,
Data: ConfigurationStatusData{},
}
if _, err := os.Stat(filepath); err == nil {
if err := status.Data.load(filepath); err == nil {
return &status, nil
}
} else {
status.Data.init()
if err := status.save(); err == nil {
return &status, nil
}
}
return &status, nil
}
func (status *ConfigurationStatus) Success() error {
status.Data.init()
status.Data.PendingSince = time.Time{}
return status.save()
}
func (status *ConfigurationStatus) Failure(err string) error {
status.Data.init()
status.Data.FailureDetails = err
return status.save()
}
func (status *ConfigurationStatus) Progress() error {
status.Data.LastProgress = time.Now()
return status.save()
}
func (status *ConfigurationStatus) RecordLinkClicked(link uint) error {
if !status.Data.hasLinkClicked(link) {
status.Data.setClickedLink(link)
return status.save()
}
return nil
}
func (status *ConfigurationStatus) save() error {
return status.Data.save(status.FilePath)
}
func (data *ConfigurationStatusData) init() {
data.PendingSince = time.Now()
data.LastProgress = time.Time{}
data.Autoconf = ""
data.ClickedLink = 0
data.ReportSent = false
data.ReportClick = false
data.FailureDetails = ""
}
func (data *ConfigurationStatusData) load(filepath string) error {
f, err := os.Open(filepath) // nolint: gosec
if err != nil {
return err
}
defer func() { _ = f.Close() }()
return json.NewDecoder(f).Decode(data)
}
func (data *ConfigurationStatusData) save(filepath string) error {
f, err := os.Create(filepath) // nolint: gosec
if err != nil {
return err
}
defer func() { _ = f.Close() }()
return json.NewEncoder(f).Encode(data)
}
func (data *ConfigurationStatusData) setClickedLink(pos uint) {
data.ClickedLink |= 1 << pos
}
func (data *ConfigurationStatusData) hasLinkClicked(pos uint) bool {
val := data.ClickedLink & (1 << pos)
return val > 0
}

View File

@ -25,6 +25,7 @@ import (
"fmt" "fmt"
"io" "io"
"net" "net"
"path/filepath"
"strings" "strings"
"sync/atomic" "sync/atomic"
"time" "time"
@ -92,6 +93,8 @@ type User struct {
maxSyncMemory uint64 maxSyncMemory uint64
panicHandler async.PanicHandler panicHandler async.PanicHandler
configStatus *ConfigurationStatus
} }
// New returns a new user. // New returns a new user.
@ -104,6 +107,7 @@ func New(
crashHandler async.PanicHandler, crashHandler async.PanicHandler,
showAllMail bool, showAllMail bool,
maxSyncMemory uint64, maxSyncMemory uint64,
cacheDir string,
) (*User, error) { ) (*User, error) {
logrus.WithField("userID", apiUser.ID).Info("Creating new user") logrus.WithField("userID", apiUser.ID).Info("Creating new user")
@ -125,6 +129,12 @@ func New(
"numLabels": len(apiLabels), "numLabels": len(apiLabels),
}).Info("Creating user object") }).Info("Creating user object")
configStatusFile := filepath.Join(cacheDir, apiUser.ID+".json")
configStatus, err := LoadConfigurationStatus(configStatusFile)
if err != nil {
return nil, fmt.Errorf("failed to init configuration status file: %w", err)
}
// Create the user object. // Create the user object.
user := &User{ user := &User{
log: logrus.WithField("userID", apiUser.ID), log: logrus.WithField("userID", apiUser.ID),
@ -157,6 +167,8 @@ func New(
maxSyncMemory: maxSyncMemory, maxSyncMemory: maxSyncMemory,
panicHandler: crashHandler, panicHandler: crashHandler,
configStatus: configStatus,
} }
// Initialize the user's update channels for its current address mode. // Initialize the user's update channels for its current address mode.

View File

@ -143,7 +143,7 @@ func withUser(tb testing.TB, ctx context.Context, _ *server.Server, m *proton.Ma
vaultUser, err := v.AddUser(apiUser.ID, username, username+"@pm.me", apiAuth.UID, apiAuth.RefreshToken, saltedKeyPass) vaultUser, err := v.AddUser(apiUser.ID, username, username+"@pm.me", apiAuth.UID, apiAuth.RefreshToken, saltedKeyPass)
require.NoError(tb, err) require.NoError(tb, err)
user, err := New(ctx, vaultUser, client, nil, apiUser, nil, true, vault.DefaultMaxSyncMemory) user, err := New(ctx, vaultUser, client, nil, apiUser, nil, true, vault.DefaultMaxSyncMemory, tb.TempDir())
require.NoError(tb, err) require.NoError(tb, err)
defer user.Close() defer user.Close()