GODT-22: Frontend-backend

- GODT-1246 Implement settings view.
- GODT-1257 GODT-1246: Account and Help view
- GODT-1298: Minimal working build (panics)
- GODT-1298: loading QML (needs Popup window)
- GODT-1298: WARN: Adding PopupWindow not possible!
    In therecipe qt the `quickwidgets` classes are within `quick` module, but
    forgot to add library and include paths into cgo flags. Therefore
    compilation fails and it would be hard to patch therecipe in order to
    fix it.

    I am not sure if rewrite PopupWindow into go would make any difference,
    therefore I decided to use normal QML Window without borders.
- GODT-1298: Rework status window, add backend props, slots and signals.
- GODT-1298: Users
- GODT-1298: Login
- GODT-1298: WIP Help and bug report
- GODT-1178: MacOS dock icon control
- GODT-1298: Help, bug report, update and events
- GODT-1298: Apple Mail config and Settings (without cache on disk)
This commit is contained in:
Jakub
2021-08-09 14:40:56 +02:00
parent 0a9748a15d
commit e0d07d67a0
76 changed files with 4730 additions and 398 deletions

View File

@ -0,0 +1,24 @@
// 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/>.
// +build darwin
// +build build_qt
#include <stdbool.h>
void SetDockIconVisibleState(bool visible);
bool GetDockIconVisibleState();

View File

@ -0,0 +1,42 @@
// 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/>.
// +build darwin
// +build build_qt
#include "DockIcon.h"
#include <Cocoa/Cocoa.h>
void SetDockIconVisibleState(bool visible) {
if (visible) {
[NSApp setActivationPolicy: NSApplicationActivationPolicyRegular];
return;
} else {
[NSApp setActivationPolicy: NSApplicationActivationPolicyAccessory];
return;
}
}
bool GetDockIconVisibleState() {
switch ([NSApp activationPolicy]) {
case NSApplicationActivationPolicyAccessory:
case NSApplicationActivationPolicyProhibited:
return false;
case NSApplicationActivationPolicyRegular:
return true;
}
}

View File

@ -0,0 +1,33 @@
// 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/>.
// +build darwin
// +build build_qt
package dockicon
// #cgo CFLAGS: -x objective-c
// #cgo LDFLAGS: -framework Cocoa
// #include "DockIcon.h"
import "C"
func SetDockIconVisibleState(visible bool) {
C.SetDockIconVisibleState(C.bool(visible))
}
func GetDockIconVisibleState() bool {
return bool(C.GetDockIconVisibleState())
}

View File

@ -0,0 +1,26 @@
// 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/>.
// +build !darwin
// +build build_qt
package dockicon
func SetDockIconVisibleState(visible bool) {}
func GetDockIconVisibleState() bool {
return true
}

View File

@ -0,0 +1,146 @@
// 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/>.
// +build build_qt
// Package qt provides communication between Qt/QML frontend and Go backend
package qt
import (
"fmt"
"sync"
"github.com/ProtonMail/go-autostart"
"github.com/ProtonMail/proton-bridge/internal/config/settings"
"github.com/ProtonMail/proton-bridge/internal/config/useragent"
"github.com/ProtonMail/proton-bridge/internal/frontend/types"
"github.com/ProtonMail/proton-bridge/internal/locations"
"github.com/ProtonMail/proton-bridge/internal/updater"
"github.com/ProtonMail/proton-bridge/pkg/listener"
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
"github.com/sirupsen/logrus"
"github.com/therecipe/qt/qml"
"github.com/therecipe/qt/widgets"
)
type FrontendQt struct {
programName, programVersion string
panicHandler types.PanicHandler
locations *locations.Locations
settings *settings.Settings
eventListener listener.Listener
updater types.Updater
userAgent *useragent.UserAgent
bridge types.Bridger
noEncConfirmator types.NoEncConfirmator
autostart *autostart.App
restarter types.Restarter
authClient pmapi.Client
auth *pmapi.Auth
password []byte
newVersionInfo updater.VersionInfo
log *logrus.Entry
usersMtx sync.Mutex
app *widgets.QApplication
engine *qml.QQmlApplicationEngine
qml *QMLBackend
}
func New(
version,
buildVersion,
programName string,
showWindowOnStart bool,
panicHandler types.PanicHandler,
locations *locations.Locations,
settings *settings.Settings,
eventListener listener.Listener,
updater types.Updater,
userAgent *useragent.UserAgent,
bridge types.Bridger,
_ types.NoEncConfirmator,
autostart *autostart.App,
restarter types.Restarter,
) *FrontendQt {
return &FrontendQt{
programName: "Proton Mail Bridge",
programVersion: version,
log: logrus.WithField("pkg", "frontend/qt"),
panicHandler: panicHandler,
locations: locations,
settings: settings,
eventListener: eventListener,
updater: updater,
userAgent: userAgent,
bridge: bridge,
autostart: autostart,
restarter: restarter,
}
}
func (f *FrontendQt) Loop() error {
err := f.initiateQtApplication()
if err != nil {
return err
}
go func() {
defer f.panicHandler.HandlePanic()
f.watchEvents()
}()
if ret := f.app.Exec(); ret != 0 {
err := fmt.Errorf("Event loop ended with return value: %v", ret)
f.log.Warn("App exec", err)
return err
}
return nil
}
func (f *FrontendQt) NotifyManualUpdate(version updater.VersionInfo, canInstall bool) {
if canInstall {
f.qml.UpdateManualReady(version.Version.String())
} else {
f.qml.UpdateManualError()
}
}
func (f *FrontendQt) SetVersion(version updater.VersionInfo) {
f.newVersionInfo = version
f.qml.SetReleaseNotesLink(version.ReleaseNotesPage)
f.qml.SetLandingPageLink(version.LandingPage)
}
func (f *FrontendQt) NotifySilentUpdateInstalled() {
f.qml.UpdateSilentRestartNeeded()
}
func (f *FrontendQt) NotifySilentUpdateError(err error) {
f.log.WithError(err).Warn("Update failed, asking for manual.")
f.qml.UpdateManualError()
}
func (f *FrontendQt) WaitUntilFrontendIsReady() {
// TODO: Implement
}

View File

@ -0,0 +1,85 @@
// 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/>.
// +build build_qt
// Package qt provides communication between Qt/QML frontend and Go backend
package qt
import (
"strings"
"github.com/ProtonMail/proton-bridge/internal/events"
)
func (f *FrontendQt) watchEvents() {
f.WaitUntilFrontendIsReady()
errorCh := f.eventListener.ProvideChannel(events.ErrorEvent)
credentialsErrorCh := f.eventListener.ProvideChannel(events.CredentialsErrorEvent)
noActiveKeyForRecipientCh := f.eventListener.ProvideChannel(events.NoActiveKeyForRecipientEvent)
internetOffCh := f.eventListener.ProvideChannel(events.InternetOffEvent)
internetOnCh := f.eventListener.ProvideChannel(events.InternetOnEvent)
secondInstanceCh := f.eventListener.ProvideChannel(events.SecondInstanceEvent)
restartBridgeCh := f.eventListener.ProvideChannel(events.RestartBridgeEvent)
addressChangedCh := f.eventListener.ProvideChannel(events.AddressChangedEvent)
addressChangedLogoutCh := f.eventListener.ProvideChannel(events.AddressChangedLogoutEvent)
logoutCh := f.eventListener.ProvideChannel(events.LogoutEvent)
updateApplicationCh := f.eventListener.ProvideChannel(events.UpgradeApplicationEvent)
userChangedCh := f.eventListener.ProvideChannel(events.UserRefreshEvent)
certIssue := f.eventListener.ProvideChannel(events.TLSCertIssue)
for {
select {
case errorDetails := <-errorCh:
if strings.Contains(errorDetails, "IMAP failed") {
f.qml.PortIssueIMAP()
}
if strings.Contains(errorDetails, "SMTP failed") {
f.qml.PortIssueSMTP()
}
case <-credentialsErrorCh:
f.qml.NotifyHasNoKeychain()
case email := <-noActiveKeyForRecipientCh:
f.qml.NoActiveKeyForRecipient(email)
case <-internetOffCh:
f.qml.InternetOff()
case <-internetOnCh:
f.qml.InternetOn()
case <-secondInstanceCh:
f.qml.ShowMainWindow()
case <-restartBridgeCh:
f.restart()
case address := <-addressChangedCh:
f.qml.AddressChanged(address)
case address := <-addressChangedLogoutCh:
f.qml.AddressChangedLogout(address)
case userID := <-logoutCh:
user, err := f.bridge.GetUser(userID)
if err != nil {
return
}
f.qml.UserDisconnected(user.Username())
case <-updateApplicationCh:
f.updateForce()
case userID := <-userChangedCh:
f.userChanged(userID)
case <-certIssue:
f.qml.ApiCertIssue()
}
}
}

View File

@ -0,0 +1,45 @@
// 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/>.
// +build build_qt
package qt
func (f *FrontendQt) setVersion() {
f.qml.SetVersion(f.programVersion)
}
func (f *FrontendQt) setLogsPath() {
path, err := f.locations.ProvideLogsPath()
if err != nil {
f.log.WithError(err).Error("Cannot update path folder")
return
}
f.qml.SetLogsPath(path)
}
func (f *FrontendQt) setLicensePath() {
f.qml.SetLicensePath(f.locations.GetLicenseFilePath())
}
func (f *FrontendQt) setCurrentEmailClient() {
f.qml.SetCurrentEmailClient(f.userAgent.String())
}
func (f *FrontendQt) reportBug(description, address, emailClient string, includeLogs bool) {
//TODO
}

View File

@ -0,0 +1,71 @@
// 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/>.
// +build build_qt
package qt
import (
"errors"
qmlLog "github.com/ProtonMail/proton-bridge/internal/frontend/qt/log"
"github.com/therecipe/qt/core"
"github.com/therecipe/qt/qml"
"github.com/therecipe/qt/quickcontrols2"
"github.com/therecipe/qt/widgets"
"os"
)
func (f *FrontendQt) initiateQtApplication() error {
qmlLog.InstallMessageHandler()
f.app = widgets.NewQApplication(len(os.Args), os.Args)
core.QCoreApplication_SetApplicationName(f.programName)
core.QCoreApplication_SetApplicationVersion(f.programVersion)
// High DPI scaling for windows.
core.QCoreApplication_SetAttribute(core.Qt__AA_EnableHighDpiScaling, false)
// Software OpenGL: to avoid dedicated GPU.
core.QCoreApplication_SetAttribute(core.Qt__AA_UseSoftwareOpenGL, true)
// Bridge runs background, no window is needed to be opened.
f.app.SetQuitOnLastWindowClosed(false)
// QML Engine and path
f.engine = qml.NewQQmlApplicationEngine(f.app)
f.qml = NewQMLBackend(nil)
f.qml.setup(f)
f.engine.RootContext().SetContextProperty("go", f.qml)
f.engine.AddImportPath("qrc:/qml/")
f.engine.AddPluginPath("qrc:/qml/")
// Add style: if colorScheme / style is forgotten we should fallback to
// default style and should be Proton
quickcontrols2.QQuickStyle_AddStylePath("qrc:/qml/")
quickcontrols2.QQuickStyle_SetStyle("Proton")
f.engine.Load(core.NewQUrl3("qrc:/qml/Bridge.qml", 0))
// Check QML is loaded properly.
if len(f.engine.RootObjects()) == 0 {
return errors.New("QML not loaded properly")
}
return nil
}

View File

@ -0,0 +1,83 @@
// 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/>.
// +build !build_qt
package qt
import (
"fmt"
"net/http"
"github.com/ProtonMail/go-autostart"
"github.com/ProtonMail/proton-bridge/internal/config/settings"
"github.com/ProtonMail/proton-bridge/internal/config/useragent"
"github.com/ProtonMail/proton-bridge/internal/frontend/types"
"github.com/ProtonMail/proton-bridge/internal/locations"
"github.com/ProtonMail/proton-bridge/internal/updater"
"github.com/ProtonMail/proton-bridge/pkg/listener"
"github.com/sirupsen/logrus"
)
var log = logrus.WithField("pkg", "frontend-nogui") //nolint[gochecknoglobals]
type FrontendHeadless struct{}
func New(
version,
buildVersion,
programName string,
showWindowOnStart bool,
panicHandler types.PanicHandler,
locations *locations.Locations,
settings *settings.Settings,
eventListener listener.Listener,
updater types.Updater,
userAgent *useragent.UserAgent,
bridge types.Bridger,
noEncConfirmator types.NoEncConfirmator,
autostart *autostart.App,
restarter types.Restarter,
) *FrontendHeadless {
return &FrontendHeadless{}
}
func (s *FrontendHeadless) Loop() error {
log.Info("Check status on localhost:8081")
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Bridge is running")
})
return http.ListenAndServe(":8081", nil)
}
func (s *FrontendHeadless) NotifyManualUpdate(update updater.VersionInfo, canInstall bool) {
// NOTE: Save the update somewhere so that it can be installed when user chooses "install now".
}
func (s *FrontendHeadless) WaitUntilFrontendIsReady() {
}
func (s *FrontendHeadless) SetVersion(update updater.VersionInfo) {
}
func (s *FrontendHeadless) NotifySilentUpdateInstalled() {
}
func (s *FrontendHeadless) NotifySilentUpdateError(err error) {
}
func (s *FrontendHeadless) InstanceExistAlert() {}

View File

@ -0,0 +1,151 @@
// 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/>.
// +build build_qt
package qt
import (
"time"
"github.com/ProtonMail/proton-bridge/internal/config/settings"
"github.com/ProtonMail/proton-bridge/internal/frontend/clientconfig"
"github.com/ProtonMail/proton-bridge/pkg/keychain"
"github.com/ProtonMail/proton-bridge/pkg/ports"
)
func (f *FrontendQt) setIsDiskCacheEnabled() {
//TODO
}
func (f *FrontendQt) setDiskCachePath() {
//TODO
}
func (f *FrontendQt) changeLocalCache(enableDiskCache bool, diskCachePath string) {
//TODO
}
func (f *FrontendQt) setIsAutostartOn() {
f.qml.SetIsAutostartOn(f.autostart.IsEnabled())
}
func (f *FrontendQt) toggleAutostart(makeItEnabled bool) {
defer f.qml.ToggleAutostartFinished()
if makeItEnabled == f.autostart.IsEnabled() {
f.setIsAutostartOn()
return
}
var err error
if makeItEnabled {
err = f.autostart.Enable()
} else {
err = f.autostart.Disable()
}
f.setIsAutostartOn()
if err != nil {
f.log.
WithField("makeItEnabled", makeItEnabled).
WithField("isEnabled", f.qml.IsAutostartOn()).
WithError(err).
Error("Autostart change failed")
}
}
func (f *FrontendQt) toggleDoH(makeItEnabled bool) {
if f.settings.GetBool(settings.AllowProxyKey) == makeItEnabled {
f.qml.SetIsDoHEnabled(makeItEnabled)
return
}
f.settings.SetBool(settings.AllowProxyKey, makeItEnabled)
f.restart()
}
func (f *FrontendQt) toggleUseSSLforSMTP(makeItEnabled bool) {
if f.settings.GetBool(settings.SMTPSSLKey) == makeItEnabled {
f.qml.SetUseSSLforSMTP(makeItEnabled)
return
}
f.settings.SetBool(settings.SMTPPortKey, makeItEnabled)
f.restart()
}
func (f *FrontendQt) changePorts(imapPort, smtpPort int) {
f.settings.SetInt(settings.IMAPPortKey, imapPort)
f.settings.SetInt(settings.SMTPPortKey, smtpPort)
f.restart()
}
func (f *FrontendQt) isPortFree(port int) bool {
return ports.IsPortFree(port)
}
func (f *FrontendQt) configureAppleMail(userID, address string) {
user, err := f.bridge.GetUser(userID)
if err != nil {
f.log.WithField("userID", userID).Error("Cannot configure AppleMail for user")
return
}
needRestart, err := clientconfig.ConfigureAppleMail(user, address, f.settings)
if err != nil {
f.log.WithError(err).Error("Apple Mail config failed")
}
if needRestart {
// There is delay needed for external window to open
time.Sleep(2 * time.Second)
f.restart()
}
}
func (f *FrontendQt) triggerReset() {
defer f.qml.ResetFinished()
//TODO
f.restart()
}
func (f *FrontendQt) setKeychain() {
availableKeychain := []string{}
for chain := range keychain.Helpers {
availableKeychain = append(availableKeychain, chain)
}
f.qml.SetAvailableKeychain(availableKeychain)
f.qml.SetSelectedKeychain(f.bridge.GetKeychainApp())
}
func (f *FrontendQt) selectKeychain(wantKeychain string) {
if f.bridge.GetKeychainApp() == wantKeychain {
return
}
f.bridge.SetKeychainApp(wantKeychain)
f.restart()
}
func (f *FrontendQt) restart() {
f.log.Info("Restarting bridge")
f.restarter.SetToRestart()
f.app.Exit(0)
}
func (f *FrontendQt) quit() {
f.log.Warn("Your wish is my command.. I quit!")
f.app.Exit(0)
}

View File

@ -0,0 +1,130 @@
// 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/>.
// +build build_qt
package qt
import (
"sync"
"github.com/ProtonMail/proton-bridge/internal/config/settings"
"github.com/ProtonMail/proton-bridge/internal/updater"
)
var checkingUpdates = sync.Mutex{}
func (f *FrontendQt) checkUpdates() error {
version, err := f.updater.Check()
if err != nil {
return err
}
f.SetVersion(version)
return nil
}
func (f *FrontendQt) checkUpdatesAndNotify(isRequestFromUser bool) {
checkingUpdates.Lock()
defer checkingUpdates.Lock()
defer f.qml.CheckUpdatesFinished()
if err := f.checkUpdates(); err != nil {
f.log.WithError(err).Error("An error occurred while checking updates")
if isRequestFromUser {
f.qml.UpdateManualError()
}
return
}
if !f.updater.IsUpdateApplicable(f.newVersionInfo) {
f.log.Debug("No need to update")
if isRequestFromUser {
f.qml.UpdateIsLatestVersion()
}
return
}
if !f.updater.CanInstall(f.newVersionInfo) {
f.log.Debug("A manual update is required")
f.qml.UpdateManualReady(f.newVersionInfo.Version.String())
return
}
}
func (f *FrontendQt) updateForce() {
checkingUpdates.Lock()
defer checkingUpdates.Lock()
version := ""
if err := f.checkUpdates(); err == nil {
version = f.newVersionInfo.Version.String()
}
f.qml.UpdateForce(version)
}
func (f *FrontendQt) setIsAutomaticUpdateOn() {
f.qml.SetIsAutomaticUpdateOn(f.settings.GetBool(settings.AutoUpdateKey))
}
func (f *FrontendQt) toggleAutomaticUpdate(makeItEnabled bool) {
f.qml.SetIsAutomaticUpdateOn(makeItEnabled)
isEnabled := f.settings.GetBool(settings.AutoUpdateKey)
if makeItEnabled == isEnabled {
return
}
f.settings.SetBool(settings.AutoUpdateKey, makeItEnabled)
f.checkUpdatesAndNotify(false)
}
func (f *FrontendQt) setIsBetaEnabled() {
channel := f.bridge.GetUpdateChannel()
f.qml.SetIsBetaEnabled(channel == updater.EarlyChannel)
}
func (f *FrontendQt) toggleBeta(makeItEnabled bool) {
channel := f.bridge.GetUpdateChannel()
if makeItEnabled == (channel == updater.EarlyChannel) {
f.qml.SetIsBetaEnabled(makeItEnabled)
return
}
channel = updater.StableChannel
if makeItEnabled {
channel = updater.EarlyChannel
}
needRestart, err := f.bridge.SetUpdateChannel(channel)
f.setIsBetaEnabled()
if err != nil {
f.log.WithError(err).Warn("Switching udpate channel failed.")
f.qml.UpdateManualError()
return
}
if needRestart {
f.restart()
return
}
f.checkUpdatesAndNotify(false)
}

View File

@ -0,0 +1,224 @@
// 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/>.
// +build build_qt
package qt
import (
"context"
"encoding/base64"
"github.com/ProtonMail/proton-bridge/internal/frontend/types"
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
)
func (f *FrontendQt) loadUsers() {
f.usersMtx.Lock()
defer f.usersMtx.Unlock()
f.qml.Users().clear()
for _, user := range f.bridge.GetUsers() {
f.qml.Users().addUser(newQMLUserFromBacked(f, user))
}
// If there are no active accounts.
if f.qml.Users().Count() == 0 {
f.log.Info("No active accounts")
}
}
func (f *FrontendQt) userChanged(userID string) {
f.usersMtx.Lock()
defer f.usersMtx.Unlock()
fUsers := f.qml.Users()
index := fUsers.indexByID(userID)
user, err := f.bridge.GetUser(userID)
if user == nil || err != nil {
if index >= 0 { // delete existing user
fUsers.removeUser(index)
}
return
}
if index < 0 { // add non-existing user
fUsers.addUser(newQMLUserFromBacked(f, user))
return
}
// update exiting user
fUsers.users[index].update(user)
}
func newQMLUserFromBacked(f *FrontendQt, user types.User) *QMLUser {
qu := NewQMLUser(nil)
qu.ID = user.ID()
qu.update(user)
qu.ConnectToggleSplitMode(func(activateSplitMode bool) {
go func() {
defer qu.ToggleSplitModeFinished()
if activateSplitMode == user.IsCombinedAddressMode() {
user.SwitchAddressMode()
}
qu.SetSplitMode(!user.IsCombinedAddressMode())
}()
})
qu.ConnectLogout(func() {
qu.SetLoggedIn(false)
go user.Logout()
})
qu.ConnectConfigureAppleMail(func(address string) {
go f.configureAppleMail(qu.ID, address)
})
return qu
}
func (f *FrontendQt) login(username, password string) {
var err error
f.password, err = base64.StdEncoding.DecodeString(password)
if err != nil {
f.log.WithError(err).Error("Cannot decode password")
f.qml.LoginUsernamePasswordError("Cannot decode password")
f.loginClean()
return
}
f.authClient, f.auth, err = f.bridge.Login(username, f.password)
if err != nil {
f.qml.LoginUsernamePasswordError(err.Error())
f.loginClean()
return
}
if f.auth.HasTwoFactor() {
f.qml.Login2FARequested()
return
}
if f.auth.HasMailboxPassword() {
f.qml.Login2PasswordRequested()
return
}
f.finishLogin()
}
func (f *FrontendQt) login2FA(username, code string) {
if f.auth == nil || f.authClient == nil {
f.log.Errorf("Login 2FA: authethication incomplete %p %p", f.auth, f.authClient)
f.qml.Login2FAErrorAbort("Missing authentication, try again.")
f.loginClean()
return
}
twoFA, err := base64.StdEncoding.DecodeString(code)
if err != nil {
f.log.WithError(err).Error("Cannot decode 2fa code")
f.qml.LoginUsernamePasswordError("Cannot decode 2fa code")
f.loginClean()
return
}
err = f.authClient.Auth2FA(context.Background(), string(twoFA))
if err == pmapi.ErrBad2FACodeTryAgain {
f.log.Warn("Login 2FA: retry 2fa")
f.qml.Login2FAError("")
return
}
if err == pmapi.ErrBad2FACode {
f.log.Warn("Login 2FA: abort 2fa")
f.qml.Login2FAErrorAbort("")
f.loginClean()
return
}
if err != nil {
f.log.WithError(err).Warn("Login 2FA: failed.")
f.qml.Login2FAErrorAbort(err.Error())
f.loginClean()
return
}
if f.auth.HasMailboxPassword() {
f.qml.Login2PasswordRequested()
return
}
f.finishLogin()
}
func (f *FrontendQt) login2Password(username, mboxPassword string) {
var err error
f.password, err = base64.StdEncoding.DecodeString(mboxPassword)
if err != nil {
f.log.WithError(err).Error("Cannot decode mbox password")
f.qml.LoginUsernamePasswordError("Cannot decode mbox password")
f.loginClean()
return
}
f.finishLogin()
}
func (f *FrontendQt) finishLogin() {
defer f.loginClean()
if f.auth == nil || f.authClient == nil {
f.log.Errorf("Finish login: Authethication incomplete %p %p", f.auth, f.authClient)
f.qml.Login2PasswordErrorAbort("Missing authentication, try again.")
return
}
user, err := f.bridge.FinishLogin(f.authClient, f.auth, f.password)
if err != nil {
f.log.Errorf("Authethication incomplete %p %p", f.auth, f.authClient)
f.qml.Login2PasswordErrorAbort("Missing authentication, try again.")
return
}
index := f.qml.Users().indexByID(user.ID())
if index < 0 {
qu := newQMLUserFromBacked(f, user)
qu.SetSetupGuideSeen(false)
f.qml.Users().addUser(qu)
return
}
f.qml.Users().users[index].update(user)
f.qml.LoginFinished()
}
func (f *FrontendQt) loginAbort(username string) {
f.loginClean()
}
func (f *FrontendQt) loginClean() {
f.auth = nil
f.authClient = nil
for i := range f.password {
f.password[i] = '\x00'
}
f.password = f.password[0:0]
}

View File

@ -0,0 +1,70 @@
// 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/>.
// +build build_qt
package qt
import (
"regexp"
"strings"
"github.com/therecipe/qt/core"
"github.com/therecipe/qt/gui"
)
// getCursorPos returns current mouse position to be able to use in QML
func getCursorPos() *core.QPoint {
return gui.QCursor_Pos()
}
// newQByteArrayFromString is a wrapper for new QByteArray from string.
func newQByteArrayFromString(name string) *core.QByteArray {
return core.NewQByteArray2(name, len(name))
}
var (
reMultiSpaces = regexp.MustCompile(`\s{2,}`)
reStartWithSymbol = regexp.MustCompile(`^[.,/#!$@%^&*;:{}=\-_` + "`" + `~()]`)
)
// getInitials based on webapp implementation:
// https://github.com/ProtonMail/WebClients/blob/55d96a8b4afaaa4372fc5f1ef34953f2070fd7ec/packages/shared/lib/helpers/string.ts#L145
func getInitials(fullName string) string {
words := strings.Split(
reMultiSpaces.ReplaceAllString(fullName, " "),
" ",
)
n := 0
for _, word := range words {
if !reStartWithSymbol.MatchString(word) {
words[n] = word
n++
}
}
if n == 0 {
return "?"
}
initials := words[0][0:1]
if n != 1 {
initials += words[n-1][0:1]
}
return strings.ToUpper(initials)
}

View File

@ -0,0 +1,44 @@
// 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/>.
// +build build_qt
#include "log.h"
#include "_cgo_export.h"
#include <QObject>
#include <QByteArray>
#include <QString>
#include <QVector>
#include <QtGlobal>
void messageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg)
{
Q_UNUSED( type )
Q_UNUSED( context )
QByteArray localMsg = msg.toUtf8().prepend("WHITESPACE");
logMsgPacked(
const_cast<char*>( (localMsg.constData()) +10 ),
localMsg.size()-10
);
}
void InstallMessageHandler() {
qInstallMessageHandler(messageHandler);
}

View File

@ -0,0 +1,46 @@
// 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/>.
// +build build_qt
// Package log redirects QML logs to logrus
package log
//#include "log.h"
import "C"
import (
"github.com/sirupsen/logrus"
"github.com/therecipe/qt/core"
)
var logQML = logrus.WithField("pkg", "frontent/qml")
// InstallMessageHandler is registering logQML as logger for QML calls.
func InstallMessageHandler() {
C.InstallMessageHandler()
}
//export logMsgPacked
func logMsgPacked(data *C.char, len C.int) {
logQML.Warn(C.GoStringN(data, len))
}
// logDummy is here to trigger qtmoc to create cgo instructions
type logDummy struct {
core.QObject
}

View File

@ -0,0 +1,36 @@
// 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/>.
#pragma once
#ifndef LOGRUS_QML_LOG_H
#define LOGRUS_QML_LOG_H
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif // C++
void InstallMessageHandler();
;
#ifdef __cplusplus
}
#endif // C++
#endif // LOGRUS_QML_LOG_H

View File

@ -0,0 +1,203 @@
// 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/>.
// +build build_qt
package qt
import (
"runtime"
"github.com/ProtonMail/proton-bridge/internal/bridge"
"github.com/ProtonMail/proton-bridge/internal/config/settings"
dockIcon "github.com/ProtonMail/proton-bridge/internal/frontend/qt/dockicon"
"github.com/therecipe/qt/core"
)
// QMLBackend connects QML frontend with Go backend.
type QMLBackend struct {
core.QObject
_ func() *core.QPoint `slot:"getCursorPos"`
_ func() `slot:"quit"`
_ func() `slot:"restart"`
_ bool `property:dockIconVisible`
_ QMLUserModel `property:"users"`
// TODO copy stuff from Bridge_test.qml backend object
_ string `property:"goos"`
_ func(username, password string) `slot:"login"`
_ func(username, code string) `slot:"login2FA"`
_ func(username, password string) `slot:"login2Password"`
_ func(username string) `slot:"loginAbort"`
_ func(errorMsg string) `signal:"loginUsernamePasswordError"`
_ func(errorMsg string) `signal:"loginFreeUserError"`
_ func(errorMsg string) `signal:"loginConnectionError"`
_ func() `signal:"login2FARequested"`
_ func(errorMsg string) `signal:"login2FAError"`
_ func(errorMsg string) `signal:"login2FAErrorAbort"`
_ func() `signal:"login2PasswordRequested"`
_ func(errorMsg string) `signal:"login2PasswordError"`
_ func(errorMsg string) `signal:"login2PasswordErrorAbort"`
_ func() `signal:"loginFinished"`
_ func() `signal:"internetOff"`
_ func() `signal:"internetOn"`
_ func(version string) `signal:"updateManualReady"`
_ func() `signal:"updateManualRestartNeeded"`
_ func() `signal:"updateManualError"`
_ func(version string) `signal:"updateForce"`
_ func() `signal:"updateForceError"`
_ func() `signal:"updateSilentRestartNeeded"`
_ func() `signal:"updateSilentError"`
_ func() `signal:"updateIsLatestVersion"`
_ func() `slot:"checkUpdates"`
_ func() `signal:"checkUpdatesFinished"`
_ bool `property:"isDiskCacheEnabled"`
_ string `property:"diskCachePath"`
_ func() `signal:"cacheUnavailable"`
_ func() `signal:"cacheCantMove"`
_ func() `signal:"cacheLocationChangeSuccess"`
_ func() `signal:"diskFull"`
_ func(enableDiskCache bool, diskCachePath string) `slot:"changeLocalCache"`
_ func() `signal:"changeLocalCacheFinished"`
_ bool `property:"isAutomaticUpdateOn"`
_ func(makeItActive bool) `slot:"toggleAutomaticUpdate"`
_ bool `property:"isAutostartOn"`
_ func(makeItActive bool) `slot:"toggleAutostart"`
_ func() `signal:"toggleAutostartFinished"`
_ bool `property:"isBetaEnabled"`
_ func(makeItActive bool) `slot:"toggleBeta"`
_ bool `property:"isDoHEnabled"`
_ func(makeItActive bool) `slot:"toggleDoH"`
_ bool `property:"useSSLforSMTP"`
_ func(makeItActive bool) `slot:"toggleUseSSLforSMTP"`
_ func() `signal:"toggleUseSSLFinished"`
_ string `property:"hostname"`
_ int `property:"portIMAP"`
_ int `property:"portSMTP"`
_ func(imapPort, smtpPort int) `slot:"changePorts"`
_ func(port int) bool `slot:"isPortFree"`
_ func() `signal:"changePortFinished"`
_ func() `signal:"portIssueIMAP"`
_ func() `signal:"portIssueSMTP"`
_ func() `slot:"triggerReset"`
_ func() `signal:"resetFinished"`
_ string `property:"version"`
_ string `property:"logsPath"`
_ string `property:"licensePath"`
_ string `property:"releaseNotesLink"`
_ string `property:"landingPageLink"`
_ string `property:"currentEmailClient"`
_ func() `slot:"updateCurrentMailClient"`
_ func(description, address, emailClient string, includeLogs bool) `slot:"reportBug"`
_ func() `signal:"reportBugFinished"`
_ func() `signal:"bugReportSendSuccess"`
_ func() `signal:"bugReportSendError"`
_ []string `property:"availableKeychain"`
_ string `property:"selectedKeychain"`
_ func(keychain string) `slot:"selectKeychain"`
_ func() `signal:"notifyHasNoKeychain"`
_ func(email string) `signal:noActiveKeyForRecipient`
_ func() `signal:showMainWindow`
_ func(address string) `signal:addressChanged`
_ func(address string) `signal:addressChangedLogout`
_ func(username string) `signal:userDisconnected`
_ func() `signal:apiCertIssue`
}
func (q *QMLBackend) setup(f *FrontendQt) {
q.ConnectGetCursorPos(getCursorPos)
q.ConnectQuit(f.quit)
q.ConnectRestart(f.restart)
q.ConnectIsDockIconVisible(func() bool {
return dockIcon.GetDockIconVisibleState()
})
q.ConnectSetDockIconVisible(func(visible bool) {
dockIcon.SetDockIconVisibleState(visible)
})
q.SetUsers(NewQMLUserModel(nil))
f.loadUsers()
q.SetGoos(runtime.GOOS)
q.ConnectLogin(func(u, p string) { go f.login(u, p) })
q.ConnectLogin2FA(func(u, p string) { go f.login2FA(u, p) })
q.ConnectLogin2Password(func(u, p string) { go f.login2Password(u, p) })
q.ConnectLoginAbort(func(u string) { go f.loginAbort(u) })
go f.checkUpdatesAndNotify(false)
q.ConnectCheckUpdates(func() { go f.checkUpdatesAndNotify(true) })
f.setIsDiskCacheEnabled()
f.setDiskCachePath()
q.ConnectChangeLocalCache(f.changeLocalCache)
f.setIsAutomaticUpdateOn()
q.ConnectToggleAutomaticUpdate(func(m bool) { go f.toggleAutomaticUpdate(m) })
f.setIsAutostartOn()
q.ConnectToggleAutostart(f.toggleAutostart)
f.setIsBetaEnabled()
q.ConnectToggleBeta(func(m bool) { go f.toggleBeta(m) })
q.SetIsDoHEnabled(f.settings.GetBool(settings.AllowProxyKey))
q.ConnectToggleDoH(f.toggleDoH)
q.SetUseSSLforSMTP(f.settings.GetBool(settings.SMTPSSLKey))
q.ConnectToggleUseSSLforSMTP(f.toggleUseSSLforSMTP)
q.SetHostname(bridge.Host)
q.SetPortIMAP(f.settings.GetInt(settings.IMAPPortKey))
q.SetPortSMTP(f.settings.GetInt(settings.SMTPPortKey))
q.ConnectChangePorts(f.changePorts)
q.ConnectIsPortFree(f.isPortFree)
q.ConnectTriggerReset(func() { go f.triggerReset() })
f.setVersion()
f.setLogsPath()
// release notes link is set by update
f.setLicensePath()
f.setCurrentEmailClient()
q.ConnectUpdateCurrentMailClient(func() { go f.setCurrentEmailClient() })
q.ConnectReportBug(func(d, a, e string, i bool) { go f.reportBug(d, a, e, i) })
f.setKeychain()
q.ConnectSelectKeychain(func(k string) { go f.selectKeychain(k) })
}

View File

@ -0,0 +1,135 @@
// 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/>.
// +build build_qt
package qt
import (
"github.com/ProtonMail/proton-bridge/internal/frontend/types"
"github.com/therecipe/qt/core"
)
// 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"`
users []*QMLUser
}
func (um *QMLUserModel) init() {
um.SetRoles(map[int]*core.QByteArray{
int(core.Qt__UserRole + 1): newQByteArrayFromString("object"),
})
um.ConnectRowCount(um.rowCount)
um.ConnectData(um.data)
um.ConnectGet(um.get)
um.users = []*QMLUser{}
um.setCount()
}
func (um *QMLUserModel) data(index *core.QModelIndex, property int) *core.QVariant {
if !index.IsValid() {
return core.NewQVariant()
}
return um.get(index.Row())
}
func (um *QMLUserModel) get(index int) *core.QVariant {
if index < 0 || index >= um.rowCount(nil) {
return core.NewQVariant()
}
return um.users[index].ToVariant()
}
func (um *QMLUserModel) rowCount(*core.QModelIndex) int {
return len(um.users)
}
func (um *QMLUserModel) setCount() {
um.SetCount(len(um.users))
}
func (um *QMLUserModel) addUser(user *QMLUser) {
um.BeginInsertRows(core.NewQModelIndex(), um.rowCount(nil), um.rowCount(nil))
um.users = append(um.users, user)
um.setCount()
um.EndInsertRows()
}
func (um *QMLUserModel) removeUser(row int) {
um.BeginRemoveRows(core.NewQModelIndex(), row, row)
um.users = append(um.users[:row], um.users[row+1:]...)
um.setCount()
um.EndRemoveRows()
}
func (um *QMLUserModel) clear() {
um.BeginRemoveRows(core.NewQModelIndex(), 0, um.rowCount(nil))
um.users = []*QMLUser{}
um.setCount()
um.EndRemoveRows()
}
func (um *QMLUserModel) indexByID(id string) int {
for i, qu := range um.users {
if id == qu.ID {
return i
}
}
return -1
}
// 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(address string) `slot:"configureAppleMail"`
ID string
}
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(true)
qu.SetUsedBytes(1.0) // TODO
qu.SetTotalBytes(10000.0) // TODO
qu.SetPassword(user.GetBridgePassword())
qu.SetAddresses(user.GetAddresses())
}