Files
proton-bridge/internal/frontend/qt/qml_users.go
2022-05-31 15:54:39 +02:00

298 lines
7.2 KiB
Go

// 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/>.
//go:build build_qt
// +build build_qt
package qt
import (
"sync"
"github.com/ProtonMail/proton-bridge/v2/internal/events"
"github.com/ProtonMail/proton-bridge/v2/internal/frontend/types"
"github.com/therecipe/qt/core"
)
func init() {
QMLUser_QRegisterMetaType()
QMLUserModel_QRegisterMetaType()
}
// QMLUserModel stores list of of users
type QMLUserModel struct {
core.QAbstractListModel
_ map[int]*core.QByteArray `property:"roles"`
_ int `property:"count"`
_ func() `constructor:"init"`
_ func(row int) *core.QVariant `slot:"get"`
userIDs []string
userByID map[string]*QMLUser
access sync.RWMutex
f *FrontendQt
}
func (um *QMLUserModel) init() {
um.access.Lock()
defer um.access.Unlock()
um.SetCount(0)
um.ConnectRowCount(um.rowCount)
um.ConnectRoleNames(um.roleNames)
um.ConnectData(um.data)
um.ConnectGet(um.get)
um.ConnectCount(func() int {
um.access.RLock()
defer um.access.RUnlock()
return len(um.userIDs)
})
um.userIDs = []string{}
um.userByID = map[string]*QMLUser{}
}
func (um *QMLUserModel) roleNames() map[int]*core.QByteArray {
return map[int]*core.QByteArray{
int(core.Qt__DisplayRole): core.NewQByteArray2("user", -1),
}
}
func (um *QMLUserModel) data(index *core.QModelIndex, property int) *core.QVariant {
if !index.IsValid() {
um.f.log.WithField("size", len(um.userIDs)).Info("Trying to get user by invalid index")
return core.NewQVariant()
}
return um.get(index.Row())
}
func (um *QMLUserModel) get(index int) *core.QVariant {
um.access.Lock()
defer um.access.Unlock()
if index < 0 || index >= len(um.userIDs) {
um.f.log.WithField("index", index).WithField("size", len(um.userIDs)).Info("Trying to get user by wrong index")
return core.NewQVariant()
}
u, err := um.getUserByID(um.userIDs[index])
if err != nil {
um.f.log.WithError(err).Error("Cannot get user from backend")
return core.NewQVariant()
}
return u.ToVariant()
}
func (um *QMLUserModel) getUserByID(userID string) (*QMLUser, error) {
u, ok := um.userByID[userID]
if ok {
return u, nil
}
user, err := um.f.bridge.GetUser(userID)
if err != nil {
return nil, err
}
u = newQMLUserFromBacked(um, user)
um.userByID[userID] = u
return u, nil
}
func (um *QMLUserModel) rowCount(*core.QModelIndex) int {
um.access.RLock()
defer um.access.RUnlock()
return len(um.userIDs)
}
func (um *QMLUserModel) setCount() {
um.SetCount(len(um.userIDs))
}
func (um *QMLUserModel) addUser(userID string) {
um.BeginInsertRows(core.NewQModelIndex(), len(um.userIDs), len(um.userIDs))
um.access.Lock()
if um.indexByIDNotSafe(userID) < 0 {
um.userIDs = append(um.userIDs, userID)
}
um.access.Unlock()
um.EndInsertRows()
um.setCount()
}
func (um *QMLUserModel) removeUser(row int) {
um.BeginRemoveRows(core.NewQModelIndex(), row, row)
um.access.Lock()
id := um.userIDs[row]
um.userIDs = append(um.userIDs[:row], um.userIDs[row+1:]...)
delete(um.userByID, id)
um.access.Unlock()
um.EndRemoveRows()
um.setCount()
}
func (um *QMLUserModel) clear() {
um.BeginResetModel()
um.access.Lock()
um.userIDs = []string{}
um.userByID = map[string]*QMLUser{}
um.SetCount(0)
um.access.Unlock()
um.EndResetModel()
}
func (um *QMLUserModel) load() {
um.clear()
for _, user := range um.f.bridge.GetUsers() {
um.addUser(user.ID())
// We need mark that all existing users already saw setup
// guide. This it is OK to construct QML here because it is in main thread.
u, err := um.getUserByID(user.ID())
if err != nil {
um.f.log.WithError(err).Error("Cannot get QMLUser while loading users")
}
u.SetSetupGuideSeen(true)
}
// If there are no active accounts.
if um.Count() == 0 {
um.f.log.Info("No active accounts")
}
}
func (um *QMLUserModel) userChanged(userID string) {
defer um.f.eventListener.Emit(events.UserChangeDone, userID)
index := um.indexByIDNotSafe(userID)
user, err := um.f.bridge.GetUser(userID)
if user == nil || err != nil {
if index >= 0 { // delete existing user
um.removeUser(index)
}
// if not exiting do nothing
return
}
if index < 0 { // add non-existing user
um.addUser(userID)
return
}
// update exiting user
um.userByID[userID].update(user)
}
func (um *QMLUserModel) indexByIDNotSafe(wantID string) int {
for i, id := range um.userIDs {
if id == wantID {
return i
}
}
return -1
}
func (um *QMLUserModel) indexByID(id string) int {
um.access.RLock()
defer um.access.RUnlock()
return um.indexByIDNotSafe(id)
}
// QMLUser holds data, slots and signals and for user.
type QMLUser struct {
core.QObject
_ string `property:"username"`
_ string `property:"avatarText"`
_ bool `property:"loggedIn"`
_ bool `property:"splitMode"`
_ bool `property:"setupGuideSeen"`
_ float32 `property:"usedBytes"`
_ float32 `property:"totalBytes"`
_ string `property:"password"`
_ []string `property:"addresses"`
_ func(makeItActive bool) `slot:"toggleSplitMode"`
_ func() `signal:"toggleSplitModeFinished"`
_ func() `slot:"logout"`
_ func() `slot:"remove"`
_ func(address string) `slot:"configureAppleMail"`
ID string
}
func newQMLUserFromBacked(um *QMLUserModel, user types.User) *QMLUser {
qu := NewQMLUser(um)
qu.ID = user.ID()
qu.update(user)
qu.ConnectToggleSplitMode(func(activateSplitMode bool) {
go func() {
defer um.f.panicHandler.HandlePanic()
defer qu.ToggleSplitModeFinished()
if activateSplitMode == user.IsCombinedAddressMode() {
user.SwitchAddressMode()
}
qu.SetSplitMode(!user.IsCombinedAddressMode())
}()
})
qu.ConnectLogout(func() {
qu.SetLoggedIn(false)
go func() {
defer um.f.panicHandler.HandlePanic()
user.Logout()
}()
})
qu.ConnectRemove(func() {
go func() {
defer um.f.panicHandler.HandlePanic()
// TODO: remove preferences
if err := um.f.bridge.DeleteUser(qu.ID, false); err != nil {
um.f.log.WithError(err).Error("Failed to remove user")
// TODO: notification
}
}()
})
qu.ConnectConfigureAppleMail(func(address string) {
go func() {
defer um.f.panicHandler.HandlePanic()
um.f.configureAppleMail(qu.ID, address)
}()
})
return qu
}
func (qu *QMLUser) update(user types.User) {
username := user.Username()
qu.SetAvatarText(getInitials(username))
qu.SetUsername(username)
qu.SetLoggedIn(user.IsConnected())
qu.SetSplitMode(!user.IsCombinedAddressMode())
qu.SetSetupGuideSeen(false)
qu.SetUsedBytes(float32(user.UsedBytes()))
qu.SetTotalBytes(float32(user.TotalBytes()))
qu.SetPassword(user.GetBridgePassword())
qu.SetAddresses(user.GetAddresses())
}