GODT-1554 / 1555: Implement gRPC go service and Qt 5 frontend C++ app.

WIP: updates

WIP: cache on disk and autostart.

WIP: mail, keychain and more.

WIP: updated grpc version in go mod file.

WIP: user list.

WIP: RPC service placeholder

WIP: test C++ RPC client skeleton.

Other: missing license script update.

WIP: use Qt test framework.

WIP: test for app and login calls.

WIP: test for update & cache on disk calls.

WIP: tests for mail settings calls.

WIP: all client tests.

WIP: linter fixes.

WIP: fix missing license link.

WIP: update dependency_license script for gRPC and protobuf.

WIP: removed unused file.

WIP: app & login event streaming tests.

WIP: update event stream tests.

WIP: completed event streaming tests.

GODT-1554: qt C++ frontend skeleton.

WIP: C++ backend declaration.

wip: started drafting user model.

WIP: users. not functional.

WIP: invokable methods

WIP: Exception class + backend 'injection' into QML.

WIP: switch to VCPKG to ease multi-arch compilation,  C++ RPC client skeleton.

WIP: Renaming and reorganisation

WIP:introduced new 'grpc' go frontend.

WIP: Worker & Oveerseer for thread management.

WIP: added log to C++ app.

WIP: event stream architecture on Go side.

WIP: event parsing and streamer stopping.

WIP: Moved grpc to frontend subfolder + use vcpkg for gRPC and protobuf.

WIP: windows building ok

WIP: wired a few messages

WIP: more wiring.

WIP: Fixed imports after rebase on top of devel.

WIP: wired some bool and string properties.

WIP: more properties.

WIP: wired cache on disk stuff

WIP: connect event watcher.

WIP: login

WIP: fix showSplashScreen

WIP: Wired login calls.

WIP: user list.

WIP: Refactored main().

WIP: User retrieval .

WIP: no shared pointer in user model.

WIP: fixed user count.

WIP: cached goos.

WIP: Wired autostart

WIP: beta channel toggle wired.

WIP: User removal

WIP: wired theme

WIP: implemented configure apple mail.

WIP: split mode.

WIP: fixed user updates.

WIP: fixed Quit from tray icon

WIP: wired CurrentEmailClient

WIP: wired UseSSLForSMTP

WIP: wired change ports .

WIP: wired DoH. .

WIP: wired keychain calls.

WIP: wired autoupdate option.

WIP: QML Backend clean-up.

WIP: cleanup.

WIP: moved user related files in subfolder. .

WIP: User are managed using smart pointers.

WIP: cleanup.

WIP: more cleanup.

WIP: mail events forwarding

WIP: code inspection tweaks from CLion.

WIP: moved QML, cleanup, and missing copyright notices.

WIP: Backend is not QMLBackend.

Other: fixed issues reported by Leander. [skip ci]
This commit is contained in:
Xavier Michelon
2022-05-16 10:59:45 +02:00
committed by Jakub
parent a4e54f063d
commit c11fe3e1ab
183 changed files with 53334 additions and 10851 deletions

4
.gitignore vendored
View File

@ -34,4 +34,6 @@ proton-bridge
# Jetbrains (CLion, Golang) cmake build dirs # Jetbrains (CLion, Golang) cmake build dirs
cmake-build-*/ cmake-build-*/
cmake-build-*/
# Doxygen doc files
_doc/

View File

@ -33,12 +33,10 @@ Proton Mail Bridge includes the following 3rd party software:
* [go-srp](https://github.com/ProtonMail/go-srp) available under [license](https://github.com/ProtonMail/go-srp/blob/master/LICENSE) * [go-srp](https://github.com/ProtonMail/go-srp) available under [license](https://github.com/ProtonMail/go-srp/blob/master/LICENSE)
* [go-vcard](https://github.com/ProtonMail/go-vcard) available under [license](https://github.com/ProtonMail/go-vcard/blob/master/LICENSE) * [go-vcard](https://github.com/ProtonMail/go-vcard) available under [license](https://github.com/ProtonMail/go-vcard/blob/master/LICENSE)
* [gopenpgp](https://github.com/ProtonMail/gopenpgp/v2) available under [license](https://github.com/ProtonMail/gopenpgp/v2/blob/master/LICENSE) * [gopenpgp](https://github.com/ProtonMail/gopenpgp/v2) available under [license](https://github.com/ProtonMail/gopenpgp/v2/blob/master/LICENSE)
* [proton-bridge](https://github.com/ProtonMail/proton-bridge) available under [license](https://github.com/ProtonMail/proton-bridge/blob/master/LICENSE)
* [goquery](https://github.com/PuerkitoBio/goquery) available under [license](https://github.com/PuerkitoBio/goquery/blob/master/LICENSE) * [goquery](https://github.com/PuerkitoBio/goquery) available under [license](https://github.com/PuerkitoBio/goquery/blob/master/LICENSE)
* [ishell](https://github.com/abiosoft/ishell) available under [license](https://github.com/abiosoft/ishell/blob/master/LICENSE) * [ishell](https://github.com/abiosoft/ishell) available under [license](https://github.com/abiosoft/ishell/blob/master/LICENSE)
* [readline](https://github.com/abiosoft/readline) available under [license](https://github.com/abiosoft/readline/blob/master/LICENSE)
* [go-singleinstance](https://github.com/allan-simon/go-singleinstance) available under [license](https://github.com/allan-simon/go-singleinstance/blob/master/LICENSE) * [go-singleinstance](https://github.com/allan-simon/go-singleinstance) available under [license](https://github.com/allan-simon/go-singleinstance/blob/master/LICENSE)
* [logex](https://github.com/chzyer/logex) available under [license](https://github.com/chzyer/logex/blob/master/LICENSE)
* [test](https://github.com/chzyer/test) available under [license](https://github.com/chzyer/test/blob/master/LICENSE)
* [godog](https://github.com/cucumber/godog) available under [license](https://github.com/cucumber/godog/blob/master/LICENSE) * [godog](https://github.com/cucumber/godog) available under [license](https://github.com/cucumber/godog/blob/master/LICENSE)
* [messages-go](https://github.com/cucumber/messages-go/v16) available under [license](https://github.com/cucumber/messages-go/v16/blob/master/LICENSE) * [messages-go](https://github.com/cucumber/messages-go/v16) available under [license](https://github.com/cucumber/messages-go/v16/blob/master/LICENSE)
* [go-sysinfo](https://github.com/elastic/go-sysinfo) available under [license](https://github.com/elastic/go-sysinfo/blob/master/LICENSE) * [go-sysinfo](https://github.com/elastic/go-sysinfo) available under [license](https://github.com/elastic/go-sysinfo/blob/master/LICENSE)
@ -51,9 +49,7 @@ Proton Mail Bridge includes the following 3rd party software:
* [go-sasl](https://github.com/emersion/go-sasl) available under [license](https://github.com/emersion/go-sasl/blob/master/LICENSE) * [go-sasl](https://github.com/emersion/go-sasl) available under [license](https://github.com/emersion/go-sasl/blob/master/LICENSE)
* [go-smtp](https://github.com/emersion/go-smtp) available under [license](https://github.com/emersion/go-smtp/blob/master/LICENSE) * [go-smtp](https://github.com/emersion/go-smtp) available under [license](https://github.com/emersion/go-smtp/blob/master/LICENSE)
* [go-textwrapper](https://github.com/emersion/go-textwrapper) available under [license](https://github.com/emersion/go-textwrapper/blob/master/LICENSE) * [go-textwrapper](https://github.com/emersion/go-textwrapper) available under [license](https://github.com/emersion/go-textwrapper/blob/master/LICENSE)
* [go-vcard](https://github.com/emersion/go-vcard) available under [license](https://github.com/emersion/go-vcard/blob/master/LICENSE)
* [color](https://github.com/fatih/color) available under [license](https://github.com/fatih/color/blob/master/LICENSE) * [color](https://github.com/fatih/color) available under [license](https://github.com/fatih/color/blob/master/LICENSE)
* [go-shlex](https://github.com/flynn-archive/go-shlex) available under [license](https://github.com/flynn-archive/go-shlex/blob/master/LICENSE)
* [sentry-go](https://github.com/getsentry/sentry-go) available under [license](https://github.com/getsentry/sentry-go/blob/master/LICENSE) * [sentry-go](https://github.com/getsentry/sentry-go) available under [license](https://github.com/getsentry/sentry-go/blob/master/LICENSE)
* [resty](https://github.com/go-resty/resty/v2) available under [license](https://github.com/go-resty/resty/v2/blob/master/LICENSE) * [resty](https://github.com/go-resty/resty/v2) available under [license](https://github.com/go-resty/resty/v2/blob/master/LICENSE)
* [dbus](https://github.com/godbus/dbus) available under [license](https://github.com/godbus/dbus/blob/master/LICENSE) * [dbus](https://github.com/godbus/dbus) available under [license](https://github.com/godbus/dbus/blob/master/LICENSE)
@ -66,15 +62,12 @@ Proton Mail Bridge includes the following 3rd party software:
* [go-keychain](https://github.com/keybase/go-keychain) available under [license](https://github.com/keybase/go-keychain/blob/master/LICENSE) * [go-keychain](https://github.com/keybase/go-keychain) available under [license](https://github.com/keybase/go-keychain/blob/master/LICENSE)
* [text](https://github.com/kr/text) available under [license](https://github.com/kr/text/blob/master/LICENSE) * [text](https://github.com/kr/text) available under [license](https://github.com/kr/text/blob/master/LICENSE)
* [aurora](https://github.com/logrusorgru/aurora) available under [license](https://github.com/logrusorgru/aurora/blob/master/LICENSE) * [aurora](https://github.com/logrusorgru/aurora) available under [license](https://github.com/logrusorgru/aurora/blob/master/LICENSE)
* [go-runewidth](https://github.com/mattn/go-runewidth) available under [license](https://github.com/mattn/go-runewidth/blob/master/LICENSE)
* [dns](https://github.com/miekg/dns) available under [license](https://github.com/miekg/dns/blob/master/LICENSE) * [dns](https://github.com/miekg/dns) available under [license](https://github.com/miekg/dns/blob/master/LICENSE)
* [jsondiff](https://github.com/nsf/jsondiff) available under [license](https://github.com/nsf/jsondiff/blob/master/LICENSE) * [jsondiff](https://github.com/nsf/jsondiff) available under [license](https://github.com/nsf/jsondiff/blob/master/LICENSE)
* [tablewriter](https://github.com/olekukonko/tablewriter) available under [license](https://github.com/olekukonko/tablewriter/blob/master/LICENSE)
* [errors](https://github.com/pkg/errors) available under [license](https://github.com/pkg/errors/blob/master/LICENSE) * [errors](https://github.com/pkg/errors) available under [license](https://github.com/pkg/errors/blob/master/LICENSE)
* [procfs](https://github.com/prometheus/procfs) available under [license](https://github.com/prometheus/procfs/blob/master/LICENSE) * [procfs](https://github.com/prometheus/procfs) available under [license](https://github.com/prometheus/procfs/blob/master/LICENSE)
* [du](https://github.com/ricochet2200/go-disk-usage/du) available under [license](https://github.com/ricochet2200/go-disk-usage/du/blob/master/LICENSE) * [du](https://github.com/ricochet2200/go-disk-usage/du) available under [license](https://github.com/ricochet2200/go-disk-usage/du/blob/master/LICENSE)
* [logrus](https://github.com/sirupsen/logrus) available under [license](https://github.com/sirupsen/logrus/blob/master/LICENSE) * [logrus](https://github.com/sirupsen/logrus) available under [license](https://github.com/sirupsen/logrus/blob/master/LICENSE)
* [bom](https://github.com/ssor/bom) available under [license](https://github.com/ssor/bom/blob/master/LICENSE)
* [testify](https://github.com/stretchr/testify) available under [license](https://github.com/stretchr/testify/blob/master/LICENSE) * [testify](https://github.com/stretchr/testify) available under [license](https://github.com/stretchr/testify/blob/master/LICENSE)
* [qt](https://github.com/therecipe/qt) available under [license](https://github.com/therecipe/qt/blob/master/LICENSE) * [qt](https://github.com/therecipe/qt) available under [license](https://github.com/therecipe/qt/blob/master/LICENSE)
* [cli](https://github.com/urfave/cli/v2) available under [license](https://github.com/urfave/cli/v2/blob/master/LICENSE) * [cli](https://github.com/urfave/cli/v2) available under [license](https://github.com/urfave/cli/v2/blob/master/LICENSE)

11
go.mod
View File

@ -20,12 +20,10 @@ require (
github.com/ProtonMail/go-srp v0.0.5 github.com/ProtonMail/go-srp v0.0.5
github.com/ProtonMail/go-vcard v0.0.0-20180326232728-33aaa0a0c8a5 github.com/ProtonMail/go-vcard v0.0.0-20180326232728-33aaa0a0c8a5
github.com/ProtonMail/gopenpgp/v2 v2.4.7 github.com/ProtonMail/gopenpgp/v2 v2.4.7
github.com/ProtonMail/proton-bridge v1.8.12
github.com/PuerkitoBio/goquery v1.5.1 github.com/PuerkitoBio/goquery v1.5.1
github.com/abiosoft/ishell v2.0.0+incompatible github.com/abiosoft/ishell v2.0.0+incompatible
github.com/abiosoft/readline v0.0.0-20180607040430-155bce2042db // indirect
github.com/allan-simon/go-singleinstance v0.0.0-20160830203053-79edcfdc2dfc github.com/allan-simon/go-singleinstance v0.0.0-20160830203053-79edcfdc2dfc
github.com/chzyer/logex v1.1.10 // indirect
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 // indirect
github.com/cucumber/godog v0.12.1 github.com/cucumber/godog v0.12.1
github.com/cucumber/messages-go/v16 v16.0.1 github.com/cucumber/messages-go/v16 v16.0.1
github.com/elastic/go-sysinfo v1.7.1 github.com/elastic/go-sysinfo v1.7.1
@ -38,9 +36,7 @@ require (
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21 github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21
github.com/emersion/go-smtp v0.14.0 github.com/emersion/go-smtp v0.14.0
github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594 github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594
github.com/emersion/go-vcard v0.0.0-20190105225839-8856043f13c5 // indirect
github.com/fatih/color v1.9.0 github.com/fatih/color v1.9.0
github.com/flynn-archive/go-shlex v0.0.0-20150515145356-3f9db97f8568 // indirect
github.com/getsentry/sentry-go v0.12.0 github.com/getsentry/sentry-go v0.12.0
github.com/go-resty/resty/v2 v2.6.0 github.com/go-resty/resty/v2 v2.6.0
github.com/godbus/dbus v4.1.0+incompatible github.com/godbus/dbus v4.1.0+incompatible
@ -53,15 +49,12 @@ require (
github.com/keybase/go-keychain v0.0.0 github.com/keybase/go-keychain v0.0.0
github.com/kr/text v0.2.0 // indirect github.com/kr/text v0.2.0 // indirect
github.com/logrusorgru/aurora v2.0.3+incompatible github.com/logrusorgru/aurora v2.0.3+incompatible
github.com/mattn/go-runewidth v0.0.9 // indirect
github.com/miekg/dns v1.1.41 github.com/miekg/dns v1.1.41
github.com/nsf/jsondiff v0.0.0-20200515183724-f29ed568f4ce github.com/nsf/jsondiff v0.0.0-20200515183724-f29ed568f4ce
github.com/olekukonko/tablewriter v0.0.4 // indirect
github.com/pkg/errors v0.9.1 github.com/pkg/errors v0.9.1
github.com/prometheus/procfs v0.7.3 // indirect github.com/prometheus/procfs v0.7.3 // indirect
github.com/ricochet2200/go-disk-usage/du v0.0.0-20210707232629-ac9918953285 github.com/ricochet2200/go-disk-usage/du v0.0.0-20210707232629-ac9918953285
github.com/sirupsen/logrus v1.7.0 github.com/sirupsen/logrus v1.7.0
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect
github.com/stretchr/testify v1.7.0 github.com/stretchr/testify v1.7.0
github.com/therecipe/qt v0.0.0-20200701200531-7f61353ee73e github.com/therecipe/qt v0.0.0-20200701200531-7f61353ee73e
github.com/urfave/cli/v2 v2.2.0 github.com/urfave/cli/v2 v2.2.0
@ -73,7 +66,7 @@ require (
golang.org/x/text v0.3.7 golang.org/x/text v0.3.7
google.golang.org/grpc v1.46.2 google.golang.org/grpc v1.46.2
google.golang.org/protobuf v1.28.0 google.golang.org/protobuf v1.28.0
howett.net/plist v1.0.0 howett.net/plist v1.0.0 // indirect
) )
replace ( replace (

16
go.sum
View File

@ -31,6 +31,8 @@ github.com/ProtonMail/docker-credential-helpers v1.1.0/go.mod h1:mK0aBveCxhnQ756
github.com/ProtonMail/go-autostart v0.0.0-20181114175602-c5272053443a h1:fXK2KsfnkBV9Nh+9SKzHchYjuE9s0vI20JG1mbtEAcc= github.com/ProtonMail/go-autostart v0.0.0-20181114175602-c5272053443a h1:fXK2KsfnkBV9Nh+9SKzHchYjuE9s0vI20JG1mbtEAcc=
github.com/ProtonMail/go-autostart v0.0.0-20181114175602-c5272053443a/go.mod h1:oTGdE7/DlWIr23G0IKW3OXK9wZ5Hw1GGiaJFccTvZi4= github.com/ProtonMail/go-autostart v0.0.0-20181114175602-c5272053443a/go.mod h1:oTGdE7/DlWIr23G0IKW3OXK9wZ5Hw1GGiaJFccTvZi4=
github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo= github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo=
github.com/ProtonMail/go-crypto v0.0.0-20210512092938-c05353c2d58c/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo=
github.com/ProtonMail/go-crypto v0.0.0-20210707164159-52430bf6b52c/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo=
github.com/ProtonMail/go-crypto v0.0.0-20211221144345-a4f6767435ab h1:5FiL/TCaiKCss/BLMIACDxxadYrx767l9kh0qYX+sLQ= github.com/ProtonMail/go-crypto v0.0.0-20211221144345-a4f6767435ab h1:5FiL/TCaiKCss/BLMIACDxxadYrx767l9kh0qYX+sLQ=
github.com/ProtonMail/go-crypto v0.0.0-20211221144345-a4f6767435ab/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo= github.com/ProtonMail/go-crypto v0.0.0-20211221144345-a4f6767435ab/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo=
github.com/ProtonMail/go-crypto v0.0.0-20220113124808-70ae35bab23f/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo= github.com/ProtonMail/go-crypto v0.0.0-20220113124808-70ae35bab23f/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo=
@ -54,8 +56,11 @@ github.com/ProtonMail/go-srp v0.0.5 h1:xhUioxZgDbCnpo9JehyFhwwsn9JLWkUGfB0oiKXgi
github.com/ProtonMail/go-srp v0.0.5/go.mod h1:06iYHtLXW8vjLtccWj++x3MKy65sIT8yZd7nrJF49rs= github.com/ProtonMail/go-srp v0.0.5/go.mod h1:06iYHtLXW8vjLtccWj++x3MKy65sIT8yZd7nrJF49rs=
github.com/ProtonMail/go-vcard v0.0.0-20180326232728-33aaa0a0c8a5 h1:Uga1DHFN4GUxuDQr0F71tpi8I9HqPIlZodZAI1lR6VQ= github.com/ProtonMail/go-vcard v0.0.0-20180326232728-33aaa0a0c8a5 h1:Uga1DHFN4GUxuDQr0F71tpi8I9HqPIlZodZAI1lR6VQ=
github.com/ProtonMail/go-vcard v0.0.0-20180326232728-33aaa0a0c8a5/go.mod h1:oeP9CMN+ajWp5jKp1kue5daJNwMMxLF+ujPaUIoJWlA= github.com/ProtonMail/go-vcard v0.0.0-20180326232728-33aaa0a0c8a5/go.mod h1:oeP9CMN+ajWp5jKp1kue5daJNwMMxLF+ujPaUIoJWlA=
github.com/ProtonMail/gopenpgp/v2 v2.2.2/go.mod h1:ajUlBGvxMH1UBZnaYO3d1FSVzjiC6kK9XlZYGiDCvpM=
github.com/ProtonMail/gopenpgp/v2 v2.4.1 h1:b3El0zabaKi73u4sRnb3hOOUczuKuYpN8wnp7wRsZSc= github.com/ProtonMail/gopenpgp/v2 v2.4.1 h1:b3El0zabaKi73u4sRnb3hOOUczuKuYpN8wnp7wRsZSc=
github.com/ProtonMail/gopenpgp/v2 v2.4.1/go.mod h1:RFjoVjfhV8f78tjz/fLrp/OXkugL3QmWsiJq/fsQYA4= github.com/ProtonMail/gopenpgp/v2 v2.4.1/go.mod h1:RFjoVjfhV8f78tjz/fLrp/OXkugL3QmWsiJq/fsQYA4=
github.com/ProtonMail/proton-bridge v1.8.12 h1:wR/WguXfVT2qiL+4xcSIlN1XqH49zl81hD0CdVnQdfw=
github.com/ProtonMail/proton-bridge v1.8.12/go.mod h1:ZeGTC11l/KTGLP5aASnS7dxu3T1z5HORA048DpwbVoI=
github.com/ProtonMail/gopenpgp/v2 v2.4.7 h1:V3xeelvXgJiZXZuPtSSE+uYbtPw4RmbmyPqXDAESPhg= github.com/ProtonMail/gopenpgp/v2 v2.4.7 h1:V3xeelvXgJiZXZuPtSSE+uYbtPw4RmbmyPqXDAESPhg=
github.com/ProtonMail/gopenpgp/v2 v2.4.7/go.mod h1:ZW1KxHNG6q5LMgFKf9Ap/d2eVYeyGf5+fAUEAjJWtmo= github.com/ProtonMail/gopenpgp/v2 v2.4.7/go.mod h1:ZW1KxHNG6q5LMgFKf9Ap/d2eVYeyGf5+fAUEAjJWtmo=
github.com/PuerkitoBio/goquery v1.5.1 h1:PSPBGne8NIUWw+/7vFBV+kG2J/5MOjbzc7154OaKCSE= github.com/PuerkitoBio/goquery v1.5.1 h1:PSPBGne8NIUWw+/7vFBV+kG2J/5MOjbzc7154OaKCSE=
@ -150,6 +155,7 @@ github.com/emersion/go-imap-quota v0.0.0-20210203125329-619074823f3c h1:khcEdu1y
github.com/emersion/go-imap-quota v0.0.0-20210203125329-619074823f3c/go.mod h1:iApyhIQBiU4XFyr+3kdJyyGqle82TbQyuP2o+OZHrV0= github.com/emersion/go-imap-quota v0.0.0-20210203125329-619074823f3c/go.mod h1:iApyhIQBiU4XFyr+3kdJyyGqle82TbQyuP2o+OZHrV0=
github.com/emersion/go-imap-unselect v0.0.0-20171113212723-b985794e5f26 h1:FiSb8+XBQQSkcX3ubr+1tAtlRJBYaFmRZqOAweZ9Wy8= github.com/emersion/go-imap-unselect v0.0.0-20171113212723-b985794e5f26 h1:FiSb8+XBQQSkcX3ubr+1tAtlRJBYaFmRZqOAweZ9Wy8=
github.com/emersion/go-imap-unselect v0.0.0-20171113212723-b985794e5f26/go.mod h1:+gnnZx3Mg3MnCzZrv0eZdp5puxXQUgGT/6N6L7ShKfM= github.com/emersion/go-imap-unselect v0.0.0-20171113212723-b985794e5f26/go.mod h1:+gnnZx3Mg3MnCzZrv0eZdp5puxXQUgGT/6N6L7ShKfM=
github.com/emersion/go-mbox v1.0.2/go.mod h1:Yp9IVuuOYLEuMv4yjgDHvhb5mHOcYH6x92Oas3QqEZI=
github.com/emersion/go-sasl v0.0.0-20191210011802-430746ea8b9b/go.mod h1:G/dpzLu16WtQpBfQ/z3LYiYJn3ZhKSGWn83fyoyQe/k= github.com/emersion/go-sasl v0.0.0-20191210011802-430746ea8b9b/go.mod h1:G/dpzLu16WtQpBfQ/z3LYiYJn3ZhKSGWn83fyoyQe/k=
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21 h1:OJyUGMJTzHTd1XQp98QTaHernxMYzRaOasRir9hUlFQ= github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21 h1:OJyUGMJTzHTd1XQp98QTaHernxMYzRaOasRir9hUlFQ=
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ= github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
@ -175,6 +181,7 @@ github.com/flynn-archive/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BMXYYRWT
github.com/flynn-archive/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:rZfgFAXFS/z/lEd6LJmf9HVZ1LkgYiHx5pHhV5DR16M= github.com/flynn-archive/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:rZfgFAXFS/z/lEd6LJmf9HVZ1LkgYiHx5pHhV5DR16M=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc= github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc=
github.com/getsentry/sentry-go v0.8.0/go.mod h1:kELm/9iCblqUYh+ZRML7PNdCvEuw24wBvJPYyi86cws=
github.com/getsentry/sentry-go v0.12.0 h1:era7g0re5iY13bHSdN/xMkyV+5zZppjRVQhZrXCaEIk= github.com/getsentry/sentry-go v0.12.0 h1:era7g0re5iY13bHSdN/xMkyV+5zZppjRVQhZrXCaEIk=
github.com/getsentry/sentry-go v0.12.0/go.mod h1:NSap0JBYWzHND8oMbyi0+XZhUalc1TBdRL1M71JZW2c= github.com/getsentry/sentry-go v0.12.0/go.mod h1:NSap0JBYWzHND8oMbyi0+XZhUalc1TBdRL1M71JZW2c=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
@ -231,6 +238,7 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
@ -241,13 +249,15 @@ github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXi
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gopherjs/gopherjs v0.0.0-20190411002643-bd77b112433e h1:XWcjeEtTFTOVA9Fs1w7n2XBftk5ib4oZrhzWk0B+3eA=
github.com/gopherjs/gopherjs v0.0.0-20190411002643-bd77b112433e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20190411002643-bd77b112433e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c h1:7lF+Vz0LqiRidnzC1Oq86fpX1q/iEv2KJdrCtttYjT4=
github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
@ -328,6 +338,7 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/labstack/echo/v4 v4.1.11/go.mod h1:i541M3Fj6f76NZtHSj7TXnyM8n2gaodfvfxNnFqi74g=
github.com/labstack/echo/v4 v4.5.0/go.mod h1:czIriw4a0C1dFun+ObrXp7ok03xON0N1awStJ6ArI7Y= github.com/labstack/echo/v4 v4.5.0/go.mod h1:czIriw4a0C1dFun+ObrXp7ok03xON0N1awStJ6ArI7Y=
github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8= github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8=
@ -429,6 +440,7 @@ github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMB
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM= github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
@ -461,6 +473,8 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/therecipe/qt v0.0.0-20200701200531-7f61353ee73e h1:G0DQ/TRQyrEZjtLlLwevFjaRiG8eeCMlq9WXQ2OO2bk= github.com/therecipe/qt v0.0.0-20200701200531-7f61353ee73e h1:G0DQ/TRQyrEZjtLlLwevFjaRiG8eeCMlq9WXQ2OO2bk=
github.com/therecipe/qt v0.0.0-20200701200531-7f61353ee73e/go.mod h1:SUUR2j3aE1z6/g76SdD6NwACEpvCxb3fvG82eKbD6us= github.com/therecipe/qt v0.0.0-20200701200531-7f61353ee73e/go.mod h1:SUUR2j3aE1z6/g76SdD6NwACEpvCxb3fvG82eKbD6us=
github.com/therecipe/qt/internal/binding/files/docs/5.12.0 v0.0.0-20200904063919-c0c124a5770d/go.mod h1:7m8PDYDEtEVqfjoUQc2UrFqhG0CDmoVJjRlQxexndFc=
github.com/therecipe/qt/internal/binding/files/docs/5.13.0 v0.0.0-20200904063919-c0c124a5770d/go.mod h1:mH55Ek7AZcdns5KPp99O0bg+78el64YCYWHiQKrOdt4=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=

View File

@ -31,7 +31,6 @@ import (
"github.com/ProtonMail/proton-bridge/v2/internal/frontend" "github.com/ProtonMail/proton-bridge/v2/internal/frontend"
"github.com/ProtonMail/proton-bridge/v2/internal/frontend/types" "github.com/ProtonMail/proton-bridge/v2/internal/frontend/types"
"github.com/ProtonMail/proton-bridge/v2/internal/imap" "github.com/ProtonMail/proton-bridge/v2/internal/imap"
"github.com/ProtonMail/proton-bridge/v2/internal/rpc"
"github.com/ProtonMail/proton-bridge/v2/internal/smtp" "github.com/ProtonMail/proton-bridge/v2/internal/smtp"
"github.com/ProtonMail/proton-bridge/v2/internal/store" "github.com/ProtonMail/proton-bridge/v2/internal/store"
"github.com/ProtonMail/proton-bridge/v2/internal/store/cache" "github.com/ProtonMail/proton-bridge/v2/internal/store/cache"
@ -139,11 +138,6 @@ func mailLoop(b *base.Base, c *cli.Context) error { //nolint:funlen
smtpPort, useSSL, tlsConfig, smtpBackend, b.Listener).ListenAndServe() smtpPort, useSSL, tlsConfig, smtpBackend, b.Listener).ListenAndServe()
}() }()
go func() {
defer b.CrashHandler.HandlePanic()
rpc.NewServer().ListenAndServe()
}()
// We want to remove old versions if the app exits successfully. // We want to remove old versions if the app exits successfully.
b.AddTeardownAction(b.Versioner.RemoveOldVersions) b.AddTeardownAction(b.Versioner.RemoveOldVersions)
@ -158,7 +152,7 @@ func mailLoop(b *base.Base, c *cli.Context) error { //nolint:funlen
case c.Bool(flagNonInteractive): case c.Bool(flagNonInteractive):
return <-(make(chan error)) // Block forever. return <-(make(chan error)) // Block forever.
default: default:
frontendMode = "qt" frontendMode = "grpc"
} }
f := frontend.New( f := frontend.New(

View File

@ -23,7 +23,7 @@ import (
"github.com/ProtonMail/proton-bridge/v2/internal/config/settings" "github.com/ProtonMail/proton-bridge/v2/internal/config/settings"
"github.com/ProtonMail/proton-bridge/v2/internal/config/useragent" "github.com/ProtonMail/proton-bridge/v2/internal/config/useragent"
"github.com/ProtonMail/proton-bridge/v2/internal/frontend/cli" "github.com/ProtonMail/proton-bridge/v2/internal/frontend/cli"
"github.com/ProtonMail/proton-bridge/v2/internal/frontend/qt" "github.com/ProtonMail/proton-bridge/v2/internal/frontend/grpc"
"github.com/ProtonMail/proton-bridge/v2/internal/frontend/types" "github.com/ProtonMail/proton-bridge/v2/internal/frontend/types"
"github.com/ProtonMail/proton-bridge/v2/internal/locations" "github.com/ProtonMail/proton-bridge/v2/internal/locations"
"github.com/ProtonMail/proton-bridge/v2/internal/updater" "github.com/ProtonMail/proton-bridge/v2/internal/updater"
@ -39,7 +39,7 @@ type Frontend interface {
WaitUntilFrontendIsReady() WaitUntilFrontendIsReady()
} }
// New returns initialized frontend based on `frontendType`, which can be `cli` or `qt`. // New returns initialized frontend based on `frontendType`, which can be `cli` or `grpc`.
func New( func New(
version, version,
buildVersion, buildVersion,
@ -58,10 +58,9 @@ func New(
) Frontend { ) Frontend {
bridgeWrap := types.NewBridgeWrap(bridge) bridgeWrap := types.NewBridgeWrap(bridge)
switch frontendType { switch frontendType {
case "qt": case "grpc":
return qt.New( return grpc.NewService(
version, version,
buildVersion,
programName, programName,
showWindowOnStart, showWindowOnStart,
panicHandler, panicHandler,
@ -74,6 +73,7 @@ func New(
noEncConfirmator, noEncConfirmator,
restarter, restarter,
) )
case "cli": case "cli":
return cli.New( return cli.New(
panicHandler, panicHandler,

View File

@ -20,27 +20,21 @@ syntax = "proto3";
import "google/protobuf/empty.proto"; import "google/protobuf/empty.proto";
import "google/protobuf/wrappers.proto"; import "google/protobuf/wrappers.proto";
option go_package = "github.com/ProtonMail/proton-bridge/internal/rpc"; option go_package = "github.com/ProtonMail/proton-bridge/internal/grpc";
package bridgerpc; // ignored by Go, used as namespace name in C++. package grpc; // ignored by Go, used as namespace name in C++.
//********************************************************************************************************************** //**********************************************************************************************************************
// Service Declaration // Service Declaration
//********************************************************************************************************************** //**********************************************************************************************************************
service BridgeRpc { service Bridge {
// App related calls // App related calls
rpc GetCursorPos (google.protobuf.Empty) returns (PointResponse); // May be unnecessary
rpc GuiReady (google.protobuf.Empty) returns (google.protobuf.Empty); rpc GuiReady (google.protobuf.Empty) returns (google.protobuf.Empty);
rpc Quit (google.protobuf.Empty) returns (google.protobuf.Empty); rpc Quit (google.protobuf.Empty) returns (google.protobuf.Empty);
rpc Restart (google.protobuf.Empty) returns (google.protobuf.Empty); rpc Restart (google.protobuf.Empty) returns (google.protobuf.Empty);
rpc SetShowOnStartup(google.protobuf.BoolValue) returns (google.protobuf.Empty);
rpc ShowOnStartup(google.protobuf.Empty) returns (google.protobuf.BoolValue); rpc ShowOnStartup(google.protobuf.Empty) returns (google.protobuf.BoolValue);
rpc SetShowSplashScreen(google.protobuf.BoolValue) returns (google.protobuf.Empty);
rpc ShowSplashScreen(google.protobuf.Empty) returns (google.protobuf.BoolValue); rpc ShowSplashScreen(google.protobuf.Empty) returns (google.protobuf.BoolValue);
rpc SetDockIconVisible(google.protobuf.BoolValue) returns (google.protobuf.Empty);
rpc DockIconVisible(google.protobuf.Empty) returns (google.protobuf.BoolValue);
rpc SetIsFirstGuiStart(google.protobuf.BoolValue) returns (google.protobuf.Empty);
rpc IsFirstGuiStart(google.protobuf.Empty) returns (google.protobuf.BoolValue); rpc IsFirstGuiStart(google.protobuf.Empty) returns (google.protobuf.BoolValue);
rpc SetIsAutostartOn(google.protobuf.BoolValue) returns (google.protobuf.Empty); rpc SetIsAutostartOn(google.protobuf.BoolValue) returns (google.protobuf.Empty);
rpc IsAutostartOn(google.protobuf.Empty) returns (google.protobuf.BoolValue); rpc IsAutostartOn(google.protobuf.Empty) returns (google.protobuf.BoolValue);
@ -49,13 +43,13 @@ service BridgeRpc {
rpc GoOs(google.protobuf.Empty) returns (google.protobuf.StringValue); rpc GoOs(google.protobuf.Empty) returns (google.protobuf.StringValue);
rpc TriggerReset(google.protobuf.Empty) returns (google.protobuf.Empty); rpc TriggerReset(google.protobuf.Empty) returns (google.protobuf.Empty);
rpc Version(google.protobuf.Empty) returns (google.protobuf.StringValue); rpc Version(google.protobuf.Empty) returns (google.protobuf.StringValue);
rpc LogPath(google.protobuf.Empty) returns (google.protobuf.StringValue); rpc LogsPath(google.protobuf.Empty) returns (google.protobuf.StringValue);
rpc LicensePath(google.protobuf.Empty) returns (google.protobuf.StringValue); rpc LicensePath(google.protobuf.Empty) returns (google.protobuf.StringValue);
rpc ReleaseNotesLink(google.protobuf.Empty) returns (google.protobuf.StringValue); // rpc ReleaseNotesLink(google.protobuf.Empty) returns (google.protobuf.StringValue); // TODO GODT-1670 Apparently cannot be polled for now, will be sent as update.
rpc LandingPageLink(google.protobuf.Empty) returns (google.protobuf.StringValue); rpc DependencyLicensesLink(google.protobuf.Empty) returns (google.protobuf.StringValue);
// rpc LandingPageLink(google.protobuf.Empty) returns (google.protobuf.StringValue); // TODO GODT-1670 Apparently cannot be polled for now, will be sent as update.
rpc SetColorSchemeName(google.protobuf.StringValue) returns (google.protobuf.Empty); rpc SetColorSchemeName(google.protobuf.StringValue) returns (google.protobuf.Empty);
rpc ColorSchemeName(google.protobuf.Empty) returns (google.protobuf.StringValue); rpc ColorSchemeName(google.protobuf.Empty) returns (google.protobuf.StringValue); // TODO Color scheme should probably entirely be managed by the client.
rpc SetCurrentEmailClient(google.protobuf.StringValue) returns (google.protobuf.Empty);
rpc CurrentEmailClient(google.protobuf.Empty) returns (google.protobuf.StringValue); rpc CurrentEmailClient(google.protobuf.Empty) returns (google.protobuf.StringValue);
rpc ReportBug(ReportBugRequest) returns (google.protobuf.Empty); rpc ReportBug(ReportBugRequest) returns (google.protobuf.Empty);
@ -72,9 +66,7 @@ service BridgeRpc {
rpc IsAutomaticUpdateOn(google.protobuf.Empty) returns (google.protobuf.BoolValue); rpc IsAutomaticUpdateOn(google.protobuf.Empty) returns (google.protobuf.BoolValue);
// cache // cache
rpc SetIsCacheOnDiskEnabled (google.protobuf.BoolValue) returns (google.protobuf.Empty);
rpc IsCacheOnDiskEnabled (google.protobuf.Empty) returns (google.protobuf.BoolValue); rpc IsCacheOnDiskEnabled (google.protobuf.Empty) returns (google.protobuf.BoolValue);
rpc SetDiskCachePath (google.protobuf.StringValue) returns (google.protobuf.Empty);
rpc DiskCachePath(google.protobuf.Empty) returns (google.protobuf.StringValue); rpc DiskCachePath(google.protobuf.Empty) returns (google.protobuf.StringValue);
rpc ChangeLocalCache(ChangeLocalCacheRequest) returns (google.protobuf.Empty); rpc ChangeLocalCache(ChangeLocalCacheRequest) returns (google.protobuf.Empty);
@ -84,9 +76,7 @@ service BridgeRpc {
rpc SetUseSslForSmtp(google.protobuf.BoolValue) returns (google.protobuf.Empty); rpc SetUseSslForSmtp(google.protobuf.BoolValue) returns (google.protobuf.Empty);
rpc UseSslForSmtp(google.protobuf.Empty) returns (google.protobuf.BoolValue); rpc UseSslForSmtp(google.protobuf.Empty) returns (google.protobuf.BoolValue);
rpc Hostname(google.protobuf.Empty) returns (google.protobuf.StringValue); rpc Hostname(google.protobuf.Empty) returns (google.protobuf.StringValue);
rpc SetImapPort(google.protobuf.Int32Value) returns (google.protobuf.Empty);
rpc ImapPort(google.protobuf.Empty) returns (google.protobuf.Int32Value); rpc ImapPort(google.protobuf.Empty) returns (google.protobuf.Int32Value);
rpc SetSmtpPort(google.protobuf.Int32Value) returns (google.protobuf.Empty);
rpc SmtpPort(google.protobuf.Empty) returns (google.protobuf.Int32Value); rpc SmtpPort(google.protobuf.Empty) returns (google.protobuf.Int32Value);
rpc ChangePorts(ChangePortsRequest) returns (google.protobuf.Empty); rpc ChangePorts(ChangePortsRequest) returns (google.protobuf.Empty);
rpc IsPortFree(google.protobuf.Int32Value) returns (google.protobuf.BoolValue); rpc IsPortFree(google.protobuf.Int32Value) returns (google.protobuf.BoolValue);
@ -98,28 +88,21 @@ service BridgeRpc {
// User & user list // User & user list
rpc GetUserList(google.protobuf.Empty) returns (UserListResponse); rpc GetUserList(google.protobuf.Empty) returns (UserListResponse);
rpc GetUser(google.protobuf.Empty) returns (User); rpc GetUser(google.protobuf.StringValue) returns (User);
rpc SetUserSplitMode(UserSplitModeRequest) returns (google.protobuf.Empty); rpc SetUserSplitMode(UserSplitModeRequest) returns (google.protobuf.Empty);
rpc LogoutUser(google.protobuf.StringValue) returns (google.protobuf.Empty); rpc LogoutUser(google.protobuf.StringValue) returns (google.protobuf.Empty);
rpc RemoveUser(google.protobuf.StringValue) returns (google.protobuf.Empty); rpc RemoveUser(google.protobuf.StringValue) returns (google.protobuf.Empty);
rpc ConfigureUserAppleMail(ConfigureAppleMailRequest) returns (google.protobuf.Empty); rpc ConfigureUserAppleMail(ConfigureAppleMailRequest) returns (google.protobuf.Empty);
// Server -> Client event stream // Server -> Client event stream
rpc GetEvents(google.protobuf.Empty) returns (stream StreamEvent); rpc StartEventStream(google.protobuf.Empty) returns (stream StreamEvent); // Keep streaming until StopEventStream is called.
rpc StopEventStream(google.protobuf.Empty) returns (google.protobuf.Empty);
} }
//********************************************************************************************************************** //**********************************************************************************************************************
// RPC calls requests and replies messages // RPC calls requests and replies messages
//********************************************************************************************************************** //**********************************************************************************************************************
//**********************************************************
// GUI related messages
//**********************************************************
message PointResponse {
int32 x = 1;
int32 y = 2;
};
message ReportBugRequest { message ReportBugRequest {
string description = 1; string description = 1;
string address = 2; string address = 2;
@ -214,7 +197,7 @@ message StreamEvent {
message AppEvent { message AppEvent {
oneof event { oneof event {
InternetStatusEvent internetStatus = 1; InternetStatusEvent internetStatus = 1;
AutostartFinishedEvent autostartFinished = 2; ToggleAutostartFinishedEvent toggleAutostartFinished = 2;
ResetFinishedEvent resetFinished = 3; ResetFinishedEvent resetFinished = 3;
ReportBugFinishedEvent reportBugFinished = 4; ReportBugFinishedEvent reportBugFinished = 4;
ReportBugSuccessEvent reportBugSuccess = 5; ReportBugSuccessEvent reportBugSuccess = 5;
@ -227,7 +210,7 @@ message InternetStatusEvent {
bool connected = 1; bool connected = 1;
} }
message AutostartFinishedEvent {} message ToggleAutostartFinishedEvent {}
message ResetFinishedEvent {} message ResetFinishedEvent {}
message ReportBugFinishedEvent {} message ReportBugFinishedEvent {}
message ReportBugSuccessEvent {} message ReportBugSuccessEvent {}
@ -243,6 +226,7 @@ message LoginEvent {
LoginTfaRequestedEvent tfaRequested = 2; LoginTfaRequestedEvent tfaRequested = 2;
LoginTwoPasswordsRequestedEvent twoPasswordRequested = 3; LoginTwoPasswordsRequestedEvent twoPasswordRequested = 3;
LoginFinishedEvent finished = 4; LoginFinishedEvent finished = 4;
LoginFinishedEvent alreadyLoggedIn = 5;
} }
} }
@ -268,7 +252,7 @@ message LoginTfaRequestedEvent {
message LoginTwoPasswordsRequestedEvent {} message LoginTwoPasswordsRequestedEvent {}
message LoginFinishedEvent { message LoginFinishedEvent {
bool wasAlreadyLoggedIn = 1; string userID = 1;
} }
//********************************************************** //**********************************************************
@ -320,6 +304,8 @@ message CacheEvent {
CacheErrorEvent error = 1; CacheErrorEvent error = 1;
CacheLocationChangeSuccessEvent locationChangedSuccess = 2; CacheLocationChangeSuccessEvent locationChangedSuccess = 2;
ChangeLocalCacheFinishedEvent changeLocalCacheFinished = 3; ChangeLocalCacheFinishedEvent changeLocalCacheFinished = 3;
IsCacheOnDiskEnabledChanged isCacheOnDiskEnabledChanged = 4;
DiskCachePathChanged diskCachePathChanged = 5;
} }
} }
@ -337,6 +323,16 @@ message CacheLocationChangeSuccessEvent {};
message ChangeLocalCacheFinishedEvent {}; message ChangeLocalCacheFinishedEvent {};
message IsCacheOnDiskEnabledChanged {
bool enabled = 1;
}
message DiskCachePathChanged {
string path = 1;
}
//********************************************************** //**********************************************************
// Mail settings related events // Mail settings related events
//********************************************************** //**********************************************************
@ -393,11 +389,11 @@ message NoActiveKeyForRecipientEvent {
} }
message AddressChangedEvent { message AddressChangedEvent {
string address = 1; // TODO: user event ? string address = 1;
} }
message AddressChangedLogoutEvent { message AddressChangedLogoutEvent {
string address = 1; // TODO: user event ? string address = 1;
} }
message ApiCertIssueEvent {} message ApiCertIssueEvent {}
@ -419,10 +415,10 @@ message ToggleSplitModeFinishedEvent {
} }
message UserDisconnectedEvent { message UserDisconnectedEvent {
string username = 1; // TODO: isn't it userID ? string username = 1;
} }
message UserChangedEvent { message UserChangedEvent {
User user = 1; string userID = 1;
} }

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,69 @@
// Copyright (c) 2022 Proton AG
//
// This file is part of Proton Mail Bridge.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 grpc
//goland:noinspection SpellCheckingInspection
const (
serverCert = `-----BEGIN CERTIFICATE-----
MIIC5TCCAc2gAwIBAgIJAMUQK0VGexMsMA0GCSqGSIb3DQEBCwUAMBQxEjAQBgNV
BAMMCWxvY2FsaG9zdDAeFw0yMjA2MTQxNjUyNTVaFw0yMjA3MTQxNjUyNTVaMBQx
EjAQBgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
ggEBAL6T1JQ0jptq512PBLASpCLFB0px7KIzEml0oMUCkVgUF+2cayrvdBXJZnaO
SG+/JPnHDcQ/ecgqkh2Ii6a2x2kWA5KqWiV+bSHp0drXyUGJfM85muLsnrhYwJ83
HHtweoUVebRZvHn66KjaH8nBJ+YVWyYbSUhJezcg6nBSEtkW+I/XUHu4S2C7FUc5
DXPO3yWWZuZ22OZz70DY3uYE/9COuilotuKdj7XgeKDyKIvRXjPFyqGxwnnp6bXC
vWvrQdcxy0wM+vZxew3QtA/Ag9uKJU9owP6noauXw95l49lEVIA5KXVNtdaldVht
MO/QoelLZC7h79PK22zbii3x930CAwEAAaM6MDgwFAYDVR0RBA0wC4IJbG9jYWxo
b3N0MAsGA1UdDwQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDATANBgkqhkiG9w0B
AQsFAAOCAQEAW/9PE8dcAN+0C3K96Xd6Y3qOOtQhRw+WlZXhtiqMtlJfTjvuGKs9
58xuKcTvU5oobxLv+i5+4gpqLjUZZ9FBnYXZIACNVzq4PEXf+YdzcA+y6RS/rqT4
dUjsuYrScAmdXK03Duw3HWYrTp8gsJzIaYGTltUrOn0E4k/TsZb/tZ6z+oH7Fi+p
wdsI6Ut6Zwm3Z7WLn5DDk8KvFjHjZkdsCb82SFSAUVrzWo5EtbLIY/7y3A5rGp9D
t0AVpuGPo5Vn+MW1WA9HT8lhjz0v5wKGMOBi3VYW+Yx8FWHDpacvbZwVM0MjMSAd
M7SXYbNDiLF4LwPLsunoLsW133Ky7s99MA==
-----END CERTIFICATE-----`
serverKey = `-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC+k9SUNI6baudd
jwSwEqQixQdKceyiMxJpdKDFApFYFBftnGsq73QVyWZ2jkhvvyT5xw3EP3nIKpId
iIumtsdpFgOSqlolfm0h6dHa18lBiXzPOZri7J64WMCfNxx7cHqFFXm0Wbx5+uio
2h/JwSfmFVsmG0lISXs3IOpwUhLZFviP11B7uEtguxVHOQ1zzt8llmbmdtjmc+9A
2N7mBP/QjropaLbinY+14Hig8iiL0V4zxcqhscJ56em1wr1r60HXMctMDPr2cXsN
0LQPwIPbiiVPaMD+p6Grl8PeZePZRFSAOSl1TbXWpXVYbTDv0KHpS2Qu4e/Tytts
24ot8fd9AgMBAAECggEBAJFkGpOOnRU4s5YO3BavwgS8p9lFnLAJooxNa7GhSd0W
R0MBSEkTMU7FvaPI3L5T5xOfpoMHohLxV1Osrk3bt7oWD1e/GtLr5routejtIx8a
kttNKTriJhyhqSJOWy5ZGz+YqKbMpxuwLftTnVjAQX4o4MbrnjbFyHjAZdqW4sY2
jLulfEdOave6nxaEocmIkoXEjuX90LB+yNG6ncSYM3GV+IyCVw7DsoU4dLd/IRDa
4iJVF7tVdAsZqN6/EVYXpGqG0t1HI8ddacHa1qWgCG3kBB+3faxXZcDJdlRrXLUQ
4jLH8oEfXOb5YgCwyYzW2EynXEpG5vjsPmsCWJY/mIECgYEA52av81+lui97KLg+
T07XtR8zJPMkHnBNfc6ooWku/+0NuQPpUq14vqzRVut9jBHUDP3xSvrPnXsp15ZA
/mipLQLNKssTYtk90cyGqLUkrd/NPLFZLXToBfWBlfazdcJQQRIxZ2dTy5MH+HIU
Oio3LZi+iDIbdzzSlmL8PaLit20CgYEA0tYsswhq6OaWx25iu4hBMRlt6hr9qGVW
jlzCFjBhlh3YtoBti2w2fsJdU+hUpeXU327fhFmdCQFXtf+Om5CSHihmJ+mHj9O1
5Jd6zn4o8szdg5je9T4gt7KG6QdXaFJ2aMuq+SxZl1NIE+9qnf/qom4GHHZ/Nj41
vwlQu+zS5lECgYAOzSK0DoorPp5CHIbfy8tAap563pKQ394VDgL7UB8Rf7hA/V8P
SslOaP9679U4AGvv6M5mXWSqThZ/E71UiJ1Jo8Q72IGE8SBjKxHx+KQ/+vDF0RJD
NhchSnLfhMg14BgCEYfXdWSGwQDhg2qHzet5nyuQyqO3HMzbkblQt/qIgQKBgHLv
nPiQmy+SHRplO9+93MQ2d6wKwMNfUztSp9/OyjQ62xxKkO1TtbWOobAPVK4Hx+9y
EtmkvK3fFIC763M08eMM5PvXHDa1FFCkn6cYMZyDQDLwUINjNhTOdytr/CN76N8i
QHeLzN9o4D814mp1y+R2lFBJ7PmWGlilbGS2KxaxAoGAFMsb1MER+eTOUO3z05Di
lts4VRWQhq2frd/on6AcTv4idQox1RcOrKWQbRVgeQVY1SkkHhg8lN0jX3W3EfuQ
aOfyky04GbLiwO8NRHZMlORWLxlCkrUrb6Va+LQlT0JvpQbqdbu6Ix8NomG9K697
aScKmY7bGC0ki2IIdt2YZ5I=
-----END PRIVATE KEY-----`
)

View File

@ -0,0 +1,199 @@
// Copyright (c) 2022 Proton AG
//
// This file is part of Proton Mail Bridge.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 grpc
func NewInternetStatusEvent(connected bool) *StreamEvent {
return appEvent(&AppEvent{Event: &AppEvent_InternetStatus{InternetStatus: &InternetStatusEvent{Connected: connected}}})
}
func NewToggleAutostartFinishedEvent() *StreamEvent {
return appEvent(&AppEvent{Event: &AppEvent_ToggleAutostartFinished{ToggleAutostartFinished: &ToggleAutostartFinishedEvent{}}})
}
func NewResetFinishedEvent() *StreamEvent {
return appEvent(&AppEvent{Event: &AppEvent_ResetFinished{ResetFinished: &ResetFinishedEvent{}}})
}
func NewReportBugFinishedEvent() *StreamEvent {
return appEvent(&AppEvent{Event: &AppEvent_ReportBugFinished{ReportBugFinished: &ReportBugFinishedEvent{}}})
}
func NewReportBugSuccessEvent() *StreamEvent {
return appEvent(&AppEvent{Event: &AppEvent_ReportBugSuccess{ReportBugSuccess: &ReportBugSuccessEvent{}}})
}
func NewReportBugErrorEvent() *StreamEvent {
return appEvent(&AppEvent{Event: &AppEvent_ReportBugError{ReportBugError: &ReportBugErrorEvent{}}})
}
func NewShowMainWindowEvent() *StreamEvent {
return appEvent(&AppEvent{Event: &AppEvent_ShowMainWindow{ShowMainWindow: &ShowMainWindowEvent{}}})
}
func NewLoginError(err LoginErrorType, message string) *StreamEvent {
return loginEvent(&LoginEvent{Event: &LoginEvent_Error{Error: &LoginErrorEvent{Type: err, Message: message}}})
}
func NewLoginTfaRequestedEvent(username string) *StreamEvent {
return loginEvent(&LoginEvent{Event: &LoginEvent_TfaRequested{TfaRequested: &LoginTfaRequestedEvent{Username: username}}})
}
func NewLoginTwoPasswordsRequestedEvent() *StreamEvent {
return loginEvent(&LoginEvent{Event: &LoginEvent_TwoPasswordRequested{}})
}
func NewLoginFinishedEvent(userID string) *StreamEvent {
return loginEvent(&LoginEvent{Event: &LoginEvent_Finished{Finished: &LoginFinishedEvent{UserID: userID}}})
}
func NewLoginAlreadyLoggedInEvent(userID string) *StreamEvent {
return loginEvent(&LoginEvent{Event: &LoginEvent_AlreadyLoggedIn{AlreadyLoggedIn: &LoginFinishedEvent{UserID: userID}}})
}
func NewUpdateErrorEvent(errorType UpdateErrorType) *StreamEvent {
return updateEvent(&UpdateEvent{Event: &UpdateEvent_Error{Error: &UpdateErrorEvent{Type: errorType}}})
}
func NewUpdateManualReadyEvent(version string) *StreamEvent {
return updateEvent(&UpdateEvent{Event: &UpdateEvent_ManualReady{ManualReady: &UpdateManualReadyEvent{Version: version}}})
}
func NewUpdateManualRestartNeededEvent() *StreamEvent {
return updateEvent(&UpdateEvent{Event: &UpdateEvent_ManualRestartNeeded{ManualRestartNeeded: &UpdateManualRestartNeededEvent{}}})
}
func NewUpdateForceEvent(version string) *StreamEvent {
return updateEvent(&UpdateEvent{Event: &UpdateEvent_Force{Force: &UpdateForceEvent{Version: version}}})
}
func NewUpdateSilentRestartNeededEvent() *StreamEvent {
return updateEvent(&UpdateEvent{Event: &UpdateEvent_SilentRestartNeeded{SilentRestartNeeded: &UpdateSilentRestartNeeded{}}})
}
func NewUpdateIsLatestVersionEvent() *StreamEvent {
return updateEvent(&UpdateEvent{Event: &UpdateEvent_IsLatestVersion{IsLatestVersion: &UpdateIsLatestVersion{}}})
}
func NewUpdateCheckFinishedEvent() *StreamEvent {
return updateEvent(&UpdateEvent{Event: &UpdateEvent_CheckFinished{CheckFinished: &UpdateCheckFinished{}}})
}
func NewCacheErrorEvent(err CacheErrorType) *StreamEvent {
return cacheEvent(&CacheEvent{Event: &CacheEvent_Error{Error: &CacheErrorEvent{Type: err}}})
}
func NewCacheLocationChangeSuccessEvent() *StreamEvent {
return cacheEvent(&CacheEvent{Event: &CacheEvent_LocationChangedSuccess{LocationChangedSuccess: &CacheLocationChangeSuccessEvent{}}})
}
func NewCacheChangeLocalCacheFinishedEvent() *StreamEvent {
return cacheEvent(&CacheEvent{Event: &CacheEvent_ChangeLocalCacheFinished{ChangeLocalCacheFinished: &ChangeLocalCacheFinishedEvent{}}})
}
func NewIsCacheOnDiskEnabledChanged(enabled bool) *StreamEvent {
return cacheEvent(&CacheEvent{Event: &CacheEvent_IsCacheOnDiskEnabledChanged{IsCacheOnDiskEnabledChanged: &IsCacheOnDiskEnabledChanged{Enabled: enabled}}})
}
func NewDiskCachePathChanged(path string) *StreamEvent {
return cacheEvent(&CacheEvent{Event: &CacheEvent_DiskCachePathChanged{DiskCachePathChanged: &DiskCachePathChanged{Path: path}}})
}
func NewMailSettingsErrorEvent(err MailSettingsErrorType) *StreamEvent {
return mailSettingsEvent(&MailSettingsEvent{Event: &MailSettingsEvent_Error{Error: &MailSettingsErrorEvent{Type: err}}})
}
func NewMailSettingsUseSslForSmtpFinishedEvent() *StreamEvent { //nolint:revive,stylecheck
return mailSettingsEvent(&MailSettingsEvent{Event: &MailSettingsEvent_UseSslForSmtpFinished{UseSslForSmtpFinished: &UseSslForSmtpFinishedEvent{}}})
}
func NewMailSettingsChangePortFinishedEvent() *StreamEvent {
return mailSettingsEvent(&MailSettingsEvent{Event: &MailSettingsEvent_ChangePortsFinished{ChangePortsFinished: &ChangePortsFinishedEvent{}}})
}
func NewKeychainChangeKeychainFinishedEvent() *StreamEvent {
return keychainEvent(&KeychainEvent{Event: &KeychainEvent_ChangeKeychainFinished{ChangeKeychainFinished: &ChangeKeychainFinishedEvent{}}})
}
func NewKeychainHasNoKeychainEvent() *StreamEvent {
return keychainEvent(&KeychainEvent{Event: &KeychainEvent_HasNoKeychain{HasNoKeychain: &HasNoKeychainEvent{}}})
}
func NewKeychainRebuildKeychainEvent() *StreamEvent {
return keychainEvent(&KeychainEvent{Event: &KeychainEvent_RebuildKeychain{RebuildKeychain: &RebuildKeychainEvent{}}})
}
func NewMailNoActiveKeyForRecipientEvent(email string) *StreamEvent {
return mailEvent(&MailEvent{Event: &MailEvent_NoActiveKeyForRecipientEvent{NoActiveKeyForRecipientEvent: &NoActiveKeyForRecipientEvent{Email: email}}})
}
func NewMailAddressChangeEvent(email string) *StreamEvent {
return mailEvent(&MailEvent{Event: &MailEvent_AddressChanged{AddressChanged: &AddressChangedEvent{Address: email}}})
}
func NewMailAddressChangeLogoutEvent(email string) *StreamEvent {
return mailEvent(&MailEvent{Event: &MailEvent_AddressChangedLogout{AddressChangedLogout: &AddressChangedLogoutEvent{Address: email}}})
}
func NewMailApiCertIssue() *StreamEvent { //nolint:revive,stylecheck
return mailEvent(&MailEvent{Event: &MailEvent_ApiCertIssue{ApiCertIssue: &ApiCertIssueEvent{}}})
}
func NewUserToggleSplitModeFinishedEvent(userID string) *StreamEvent {
return userEvent(&UserEvent{Event: &UserEvent_ToggleSplitModeFinished{ToggleSplitModeFinished: &ToggleSplitModeFinishedEvent{UserID: userID}}})
}
func NewUserDisconnectedEvent(email string) *StreamEvent {
return userEvent(&UserEvent{Event: &UserEvent_UserDisconnected{UserDisconnected: &UserDisconnectedEvent{Username: email}}})
}
func NewUserChangedEvent(userID string) *StreamEvent {
return userEvent(&UserEvent{Event: &UserEvent_UserChanged{UserChanged: &UserChangedEvent{UserID: userID}}})
}
// Event category factory functions.
func appEvent(appEvent *AppEvent) *StreamEvent {
return &StreamEvent{Event: &StreamEvent_App{App: appEvent}}
}
func loginEvent(event *LoginEvent) *StreamEvent {
return &StreamEvent{Event: &StreamEvent_Login{Login: event}}
}
func updateEvent(event *UpdateEvent) *StreamEvent {
return &StreamEvent{Event: &StreamEvent_Update{Update: event}}
}
func cacheEvent(event *CacheEvent) *StreamEvent {
return &StreamEvent{Event: &StreamEvent_Cache{Cache: event}}
}
func mailSettingsEvent(event *MailSettingsEvent) *StreamEvent {
return &StreamEvent{Event: &StreamEvent_MailSettings{MailSettings: event}}
}
func keychainEvent(event *KeychainEvent) *StreamEvent {
return &StreamEvent{Event: &StreamEvent_Keychain{Keychain: event}}
}
func mailEvent(event *MailEvent) *StreamEvent {
return &StreamEvent{Event: &StreamEvent_Mail{Mail: event}}
}
func userEvent(event *UserEvent) *StreamEvent {
return &StreamEvent{Event: &StreamEvent_User{User: event}}
}

View File

@ -0,0 +1,329 @@
// Copyright (c) 2022 Proton AG
//
// This file is part of Proton Mail Bridge.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 grpc
//go:generate protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative bridge.proto
import (
"crypto/tls"
"net"
"runtime"
"strings"
"sync"
"time"
"github.com/ProtonMail/proton-bridge/v2/internal/bridge"
"github.com/ProtonMail/proton-bridge/v2/internal/config/settings"
"github.com/ProtonMail/proton-bridge/v2/internal/config/useragent"
"github.com/ProtonMail/proton-bridge/v2/internal/events"
"github.com/ProtonMail/proton-bridge/v2/internal/frontend/types"
"github.com/ProtonMail/proton-bridge/v2/internal/locations"
"github.com/ProtonMail/proton-bridge/v2/internal/updater"
"github.com/ProtonMail/proton-bridge/v2/internal/users"
"github.com/ProtonMail/proton-bridge/v2/pkg/keychain"
"github.com/ProtonMail/proton-bridge/v2/pkg/listener"
"github.com/ProtonMail/proton-bridge/v2/pkg/pmapi"
"github.com/sirupsen/logrus"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
)
// Service is the RPC service struct.
type Service struct { // nolint:structcheck
UnimplementedBridgeServer
grpcServer *grpc.Server // the gGRPC server
listener net.Listener
eventStreamCh chan *StreamEvent
eventStreamDoneCh chan struct{}
programName string
programVersion string
panicHandler types.PanicHandler
locations *locations.Locations
settings *settings.Settings
eventListener listener.Listener
updater types.Updater
userAgent *useragent.UserAgent
bridge types.Bridger
restarter types.Restarter
showOnStartup bool
authClient pmapi.Client
auth *pmapi.Auth
password []byte
// newVersionInfo updater.VersionInfo // TO-DO GODT-1670 Implement version check
log *logrus.Entry
initializing sync.WaitGroup
initializationDone sync.Once
firstTimeAutostart sync.Once
}
// NewService returns a new instance of the service.
func NewService(
version,
programName string,
showOnStartup bool,
panicHandler types.PanicHandler,
locations *locations.Locations,
settings *settings.Settings,
eventListener listener.Listener,
updater types.Updater,
userAgent *useragent.UserAgent,
bridge types.Bridger,
_ types.NoEncConfirmator,
restarter types.Restarter,
) *Service {
s := Service{
UnimplementedBridgeServer: UnimplementedBridgeServer{},
programName: programName,
programVersion: version,
panicHandler: panicHandler,
locations: locations,
settings: settings,
eventListener: eventListener,
updater: updater,
userAgent: userAgent,
bridge: bridge,
restarter: restarter,
showOnStartup: showOnStartup,
log: logrus.WithField("pkg", "grpc"),
initializing: sync.WaitGroup{},
initializationDone: sync.Once{},
firstTimeAutostart: sync.Once{},
}
s.userAgent.SetPlatform(runtime.GOOS) // TO-DO GODT-1672 In the previous Qt frontend, this routine used QSysInfo::PrettyProductName to return a more accurate description, e.g. "Windows 10" or "MacOS 10.12"
cert, err := tls.X509KeyPair([]byte(serverCert), []byte(serverKey))
if err != nil {
s.log.WithError(err).Error("could not create key pair")
panic(err)
}
s.initAutostart()
s.grpcServer = grpc.NewServer(grpc.Creds(credentials.NewTLS(&tls.Config{
Certificates: []tls.Certificate{cert},
MinVersion: tls.VersionTLS13,
})))
RegisterBridgeServer(s.grpcServer, &s)
s.listener, err = net.Listen("tcp", "127.0.0.1:9292") // Port should be configurable from the command-line.
if err != nil {
s.log.WithError(err).Error("could not create listener")
panic(err)
}
return &s
}
func (s *Service) initAutostart() {
// GODT-1507 Windows: autostart needs to be created after Qt is initialized.
// GODT-1206: if preferences file says it should be on enable it here.
// TO-DO GODT-1681 Autostart needs to be properly implement for gRPC approach.
s.firstTimeAutostart.Do(func() {
shouldAutostartBeOn := s.settings.GetBool(settings.AutostartKey)
if s.bridge.IsFirstStart() || shouldAutostartBeOn {
if err := s.bridge.EnableAutostart(); err != nil {
s.log.WithField("prefs", shouldAutostartBeOn).WithError(err).Error("Failed to enable first autostart")
}
return
}
})
}
func (s *Service) Loop() error {
defer func() {
s.settings.SetBool(settings.FirstStartGUIKey, false)
}()
go func() {
defer s.panicHandler.HandlePanic()
s.watchEvents()
}()
err := s.grpcServer.Serve(s.listener)
if err != nil {
s.log.WithError(err).Error("error serving RPC")
return err
}
return nil
}
// frontend interface functions TODO GODT-1670 Implement
func (s *Service) NotifyManualUpdate( /* update */ _ updater.VersionInfo /*canInstall */, _ bool) {}
func (s *Service) SetVersion( /* update */ updater.VersionInfo) {}
func (s *Service) NotifySilentUpdateInstalled() {}
func (s *Service) NotifySilentUpdateError(error) {}
func (s *Service) WaitUntilFrontendIsReady() {}
func (s *Service) watchEvents() { // nolint:funlen
if s.bridge.HasError(bridge.ErrLocalCacheUnavailable) {
_ = s.SendEvent(NewCacheErrorEvent(CacheErrorType_CACHE_UNAVAILABLE_ERROR))
}
errorCh := s.eventListener.ProvideChannel(events.ErrorEvent)
credentialsErrorCh := s.eventListener.ProvideChannel(events.CredentialsErrorEvent)
noActiveKeyForRecipientCh := s.eventListener.ProvideChannel(events.NoActiveKeyForRecipientEvent)
internetConnChangedCh := s.eventListener.ProvideChannel(events.InternetConnChangedEvent)
secondInstanceCh := s.eventListener.ProvideChannel(events.SecondInstanceEvent)
restartBridgeCh := s.eventListener.ProvideChannel(events.RestartBridgeEvent)
addressChangedCh := s.eventListener.ProvideChannel(events.AddressChangedEvent)
addressChangedLogoutCh := s.eventListener.ProvideChannel(events.AddressChangedLogoutEvent)
logoutCh := s.eventListener.ProvideChannel(events.LogoutEvent)
updateApplicationCh := s.eventListener.ProvideChannel(events.UpgradeApplicationEvent)
userChangedCh := s.eventListener.ProvideChannel(events.UserRefreshEvent)
certIssue := s.eventListener.ProvideChannel(events.TLSCertIssue)
// we forward events to the GUI/frontend via the gRPC event stream.
for {
select {
case errorDetails := <-errorCh:
if strings.Contains(errorDetails, "IMAP failed") {
_ = s.SendEvent(NewMailSettingsErrorEvent(MailSettingsErrorType_IMAP_PORT_ISSUE))
}
if strings.Contains(errorDetails, "SMTP failed") {
_ = s.SendEvent(NewMailSettingsErrorEvent(MailSettingsErrorType_SMTP_PORT_ISSUE))
}
case reason := <-credentialsErrorCh:
if reason == keychain.ErrMacKeychainRebuild.Error() {
_ = s.SendEvent(NewKeychainRebuildKeychainEvent())
continue
}
_ = s.SendEvent(NewKeychainHasNoKeychainEvent())
case email := <-noActiveKeyForRecipientCh:
_ = s.SendEvent(NewMailNoActiveKeyForRecipientEvent(email))
case stat := <-internetConnChangedCh:
if stat == events.InternetOff {
_ = s.SendEvent(NewInternetStatusEvent(false))
}
if stat == events.InternetOn {
_ = s.SendEvent(NewInternetStatusEvent(true))
}
case <-secondInstanceCh:
_ = s.SendEvent(NewShowMainWindowEvent())
case <-restartBridgeCh:
s.restart()
case address := <-addressChangedCh:
_ = s.SendEvent(NewMailAddressChangeEvent(address))
case address := <-addressChangedLogoutCh:
_ = s.SendEvent(NewMailAddressChangeLogoutEvent(address))
case userID := <-logoutCh:
user, err := s.bridge.GetUser(userID)
if err != nil {
return
}
_ = s.SendEvent(NewUserDisconnectedEvent(user.Username()))
case <-updateApplicationCh:
s.updateForce()
case userID := <-userChangedCh:
_ = s.SendEvent(NewUserChangedEvent(userID))
case <-certIssue:
_ = s.SendEvent(NewMailApiCertIssue())
}
}
}
func (s *Service) loginAbort() {
s.loginClean()
}
func (s *Service) loginClean() {
s.auth = nil
s.authClient = nil
for i := range s.password {
s.password[i] = '\x00'
}
s.password = s.password[0:0]
}
func (s *Service) finishLogin() {
defer s.loginClean()
if len(s.password) == 0 || s.auth == nil || s.authClient == nil {
s.log.
WithField("hasPass", len(s.password) != 0).
WithField("hasAuth", s.auth != nil).
WithField("hasClient", s.authClient != nil).
Error("Finish login: authentication incomplete")
_ = s.SendEvent(NewLoginError(LoginErrorType_TWO_PASSWORDS_ABORT, "Missing authentication, try again."))
return
}
done := make(chan string)
s.eventListener.Add(events.UserChangeDone, done)
defer s.eventListener.Remove(events.UserChangeDone, done)
user, err := s.bridge.FinishLogin(s.authClient, s.auth, s.password)
if err != nil && err != users.ErrUserAlreadyConnected {
s.log.WithError(err).Errorf("Finish login failed")
_ = s.SendEvent(NewLoginError(LoginErrorType_TWO_PASSWORDS_ABORT, err.Error()))
return
}
// The user changed should be triggered by FinishLogin, but it is not
// guaranteed when this is going to happen. Therefor we should wait
// until we receive the signal from userChanged function.
s.waitForUserChangeDone(done, user.ID())
s.log.WithField("userID", user.ID()).Debug("Login finished")
_ = s.SendEvent(NewLoginFinishedEvent(user.ID()))
if err == users.ErrUserAlreadyConnected {
s.log.WithError(err).Error("User already logged in")
_ = s.SendEvent(NewLoginAlreadyLoggedInEvent(user.ID()))
}
}
func (s *Service) waitForUserChangeDone(done <-chan string, userID string) {
for {
select {
case changedID := <-done:
if changedID == userID {
return
}
case <-time.After(2 * time.Second):
s.log.WithField("ID", userID).Warning("Login finished but user not added within 2 seconds")
return
}
}
}
func (s *Service) restart() {
s.log.Error("Restart is not implemented") // TO-DO GODT-1671 implement restart.
}
func (s *Service) checkUpdate() {
s.log.Error("checkUpdate is not implemented") // TO-DO GODT-1670 implement update check.
}
func (s *Service) updateForce() {
s.log.Error("updateForce is not implemented") // TO-DO GODT-1670 implement update.
}
func (s *Service) checkUpdateAndNotify() {
s.log.Error("checkUpdateAndNotify is not implemented") // TO-DO GODT-1670 implement update check.
}

View File

@ -0,0 +1,543 @@
// Copyright (c) 2022 Proton AG
//
// This file is part of Proton Mail Bridge.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 grpc
import (
"context"
"encoding/base64"
"runtime"
"github.com/Masterminds/semver/v3"
"github.com/ProtonMail/proton-bridge/pkg/ports"
"github.com/ProtonMail/proton-bridge/v2/internal/bridge"
"github.com/ProtonMail/proton-bridge/v2/internal/config/settings"
"github.com/ProtonMail/proton-bridge/v2/internal/frontend/theme"
"github.com/ProtonMail/proton-bridge/v2/internal/updater"
"github.com/ProtonMail/proton-bridge/v2/pkg/keychain"
"github.com/ProtonMail/proton-bridge/v2/pkg/pmapi"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/emptypb"
"google.golang.org/protobuf/types/known/wrapperspb"
)
var ErrNotImplemented = status.Errorf(codes.Unimplemented, "Not implemented")
// GuiReady implement the GuiReady gRPC service call.
func (s *Service) GuiReady(context.Context, *emptypb.Empty) (*emptypb.Empty, error) {
s.log.Info("GuiReady")
// Note nothing to be done. old Qt frontend had a sync.one
return &emptypb.Empty{}, nil
}
// Quit implement the Quit gRPC service call.
func (s *Service) Quit(ctx context.Context, empty *emptypb.Empty) (*emptypb.Empty, error) {
s.log.Info("Quit")
var err error
if s.eventStreamCh != nil {
if _, err = s.StopEventStream(ctx, empty); err != nil {
s.log.WithError(err).Error("Quit failed.")
}
}
// The following call is launched as a goroutine, as it will wait for current calls to end, including this one.
go func() { s.grpcServer.GracefulStop() }()
return &emptypb.Empty{}, err
}
// Restart implement the Restart gRPC service call.
func (s *Service) Restart(context.Context, *emptypb.Empty) (*emptypb.Empty, error) {
s.log.Info("Restart") // TO-DO-GODT-1671 handle restart.
s.restart()
return nil, ErrNotImplemented
}
func (s *Service) ShowOnStartup(context.Context, *emptypb.Empty) (*wrapperspb.BoolValue, error) {
s.log.Info("ShowOnStartup")
return wrapperspb.Bool(s.showOnStartup), nil
}
func (s *Service) ShowSplashScreen(context.Context, *emptypb.Empty) (*wrapperspb.BoolValue, error) {
s.log.Info("ShowSplashScreen")
if s.bridge.IsFirstStart() {
return wrapperspb.Bool(false), nil
}
ver, err := semver.NewVersion(s.bridge.GetLastVersion())
if err != nil {
s.log.WithError(err).WithField("last", s.bridge.GetLastVersion()).Debug("Cannot parse last version")
return wrapperspb.Bool(false), nil
}
// Current splash screen contains update on rebranding. Therefore, it
// should be shown only if the last used version was less than 2.2.0.
return wrapperspb.Bool(ver.LessThan(semver.MustParse("2.2.0"))), nil
}
func (s *Service) IsFirstGuiStart(context.Context, *emptypb.Empty) (*wrapperspb.BoolValue, error) {
s.log.Info("IsFirstGuiStart")
return wrapperspb.Bool(s.settings.GetBool(settings.FirstStartGUIKey)), nil
}
func (s *Service) SetIsAutostartOn(_ context.Context, isOn *wrapperspb.BoolValue) (*emptypb.Empty, error) {
s.log.WithField("show", isOn.Value).Info("SetIsAutostartOn")
defer func() { _ = s.SendEvent(NewToggleAutostartFinishedEvent()) }()
if isOn.Value == s.bridge.IsAutostartEnabled() {
s.initAutostart()
return &emptypb.Empty{}, nil
}
var err error
if isOn.Value {
err = s.bridge.EnableAutostart()
} else {
err = s.bridge.DisableAutostart()
}
s.initAutostart()
if err != nil {
s.log.WithField("makeItEnabled", isOn.Value).WithError(err).Error("Autostart change failed")
}
return &emptypb.Empty{}, nil
}
func (s *Service) IsAutostartOn(context.Context, *emptypb.Empty) (*wrapperspb.BoolValue, error) {
s.log.Info("IsAutostartOn")
return wrapperspb.Bool(s.bridge.IsAutostartEnabled()), nil
}
func (s *Service) SetIsBetaEnabled(_ context.Context, isEnabled *wrapperspb.BoolValue) (*emptypb.Empty, error) {
s.log.WithField("isEnabled", isEnabled.Value).Info("SetIsBetaEnabled")
channel := updater.StableChannel
if isEnabled.Value {
channel = updater.EarlyChannel
}
s.bridge.SetUpdateChannel(channel)
s.checkUpdate()
return &emptypb.Empty{}, nil
}
func (s *Service) IsBetaEnabled(context.Context, *emptypb.Empty) (*wrapperspb.BoolValue, error) {
s.log.Info("IsBetaEnabled")
return wrapperspb.Bool(s.bridge.GetUpdateChannel() == updater.EarlyChannel), nil
}
func (s *Service) GoOs(context.Context, *emptypb.Empty) (*wrapperspb.StringValue, error) {
s.log.Info("GoOs") // TO-DO We can probably get rid of this and use QSysInfo::product name
return wrapperspb.String(runtime.GOOS), nil
}
func (s *Service) TriggerReset(context.Context, *emptypb.Empty) (*emptypb.Empty, error) {
s.log.Info("TriggerReset")
return nil, ErrNotImplemented
}
func (s *Service) Version(context.Context, *emptypb.Empty) (*wrapperspb.StringValue, error) {
s.log.Info("Version")
return nil, ErrNotImplemented
}
func (s *Service) LogsPath(context.Context, *emptypb.Empty) (*wrapperspb.StringValue, error) {
s.log.Info("LogsPath")
path, err := s.locations.ProvideLogsPath()
if err != nil {
s.log.WithError(err).Error("Cannot determine logs path")
return nil, err
}
return wrapperspb.String(path), nil
}
func (s *Service) LicensePath(context.Context, *emptypb.Empty) (*wrapperspb.StringValue, error) {
s.log.Info("LicensePath")
return wrapperspb.String(s.locations.GetLicenseFilePath()), nil
}
func (s *Service) DependencyLicensesLink(context.Context, *emptypb.Empty) (*wrapperspb.StringValue, error) {
return wrapperspb.String(s.locations.GetDependencyLicensesLink()), nil
}
func (s *Service) SetColorSchemeName(_ context.Context, name *wrapperspb.StringValue) (*emptypb.Empty, error) {
s.log.WithField("ColorSchemeName", name.Value).Info("SetColorSchemeName")
if !theme.IsAvailable(theme.Theme(name.Value)) {
s.log.WithField("scheme", name.Value).Warn("Color scheme not available")
return nil, status.Error(codes.NotFound, "Color scheme not available")
}
s.settings.Set(settings.ColorScheme, name.Value)
return &emptypb.Empty{}, nil
}
func (s *Service) ColorSchemeName(context.Context, *emptypb.Empty) (*wrapperspb.StringValue, error) {
s.log.Info("ColorSchemeName")
current := s.settings.Get(settings.ColorScheme)
if !theme.IsAvailable(theme.Theme(current)) {
current = string(theme.DefaultTheme())
s.settings.Set(settings.ColorScheme, current)
}
return wrapperspb.String(current), nil
}
func (s *Service) CurrentEmailClient(context.Context, *emptypb.Empty) (*wrapperspb.StringValue, error) {
s.log.Info("CurrentEmailClient")
return wrapperspb.String(s.userAgent.String()), nil
}
func (s *Service) ReportBug(_ context.Context, report *ReportBugRequest) (*emptypb.Empty, error) {
s.log.WithField("description", report.Description).
WithField("address", report.Address).
WithField("emailClient", report.EmailClient).
WithField("includeLogs", report.IncludeLogs).
Info("ReportBug")
return &emptypb.Empty{}, nil
}
func (s *Service) Login(_ context.Context, login *LoginRequest) (*emptypb.Empty, error) {
s.log.WithField("username", login.Username).Info("Login")
go func() {
defer s.panicHandler.HandlePanic()
var err error
s.password, err = base64.StdEncoding.DecodeString(login.Password)
if err != nil {
s.log.WithError(err).Error("Cannot decode password")
_ = s.SendEvent(NewLoginError(LoginErrorType_USERNAME_PASSWORD_ERROR, "Cannot decode password"))
s.loginClean()
return
}
s.authClient, s.auth, err = s.bridge.Login(login.Username, s.password)
if err != nil {
if err == pmapi.ErrPasswordWrong {
// Remove error message since it is hardcoded in QML.
_ = s.SendEvent(NewLoginError(LoginErrorType_USERNAME_PASSWORD_ERROR, ""))
s.loginClean()
return
}
if err == pmapi.ErrPaidPlanRequired {
_ = s.SendEvent(NewLoginError(LoginErrorType_FREE_USER, ""))
s.loginClean()
return
}
_ = s.SendEvent(NewLoginError(LoginErrorType_USERNAME_PASSWORD_ERROR, err.Error()))
s.loginClean()
return
}
if s.auth.HasTwoFactor() {
_ = s.SendEvent(NewLoginTfaRequestedEvent(login.Username))
return
}
if s.auth.HasMailboxPassword() {
_ = s.SendEvent(NewLoginTwoPasswordsRequestedEvent())
return
}
s.finishLogin()
}()
return &emptypb.Empty{}, nil
}
func (s *Service) Login2FA(_ context.Context, login *LoginRequest) (*emptypb.Empty, error) {
s.log.WithField("username", login.Username).Info("Login2FA")
go func() {
defer s.panicHandler.HandlePanic()
if s.auth == nil || s.authClient == nil {
s.log.Errorf("Login 2FA: authethication incomplete %p %p", s.auth, s.authClient)
_ = s.SendEvent(NewLoginError(LoginErrorType_TFA_ABORT, "Missing authentication, try again."))
s.loginClean()
return
}
twoFA, err := base64.StdEncoding.DecodeString(login.Password)
if err != nil {
s.log.WithError(err).Error("Cannot decode 2fa code")
_ = s.SendEvent(NewLoginError(LoginErrorType_USERNAME_PASSWORD_ERROR, "Cannot decode 2fa code"))
s.loginClean()
return
}
err = s.authClient.Auth2FA(context.Background(), string(twoFA))
if err == pmapi.ErrBad2FACodeTryAgain {
s.log.Warn("Login 2FA: retry 2fa")
_ = s.SendEvent(NewLoginError(LoginErrorType_TFA_ERROR, ""))
return
}
if err == pmapi.ErrBad2FACode {
s.log.Warn("Login 2FA: abort 2fa")
_ = s.SendEvent(NewLoginError(LoginErrorType_TFA_ABORT, ""))
s.loginClean()
return
}
if err != nil {
s.log.WithError(err).Warn("Login 2FA: failed.")
_ = s.SendEvent(NewLoginError(LoginErrorType_TFA_ABORT, err.Error()))
s.loginClean()
return
}
if s.auth.HasMailboxPassword() {
_ = s.SendEvent(NewLoginTwoPasswordsRequestedEvent())
return
}
s.finishLogin()
}()
return &emptypb.Empty{}, nil
}
func (s *Service) Login2Passwords(_ context.Context, login *LoginRequest) (*emptypb.Empty, error) {
s.log.WithField("username", login.Username).Info("Login2Passwords")
go func() {
defer s.panicHandler.HandlePanic()
var err error
s.password, err = base64.StdEncoding.DecodeString(login.Password)
if err != nil {
s.log.WithError(err).Error("Cannot decode mbox password")
_ = s.SendEvent(NewLoginError(LoginErrorType_USERNAME_PASSWORD_ERROR, "Cannot decode mbox password"))
s.loginClean()
return
}
s.finishLogin()
}()
return &emptypb.Empty{}, nil
}
func (s *Service) LoginAbort(_ context.Context, loginAbort *LoginAbortRequest) (*emptypb.Empty, error) {
s.log.WithField("username", loginAbort.Username).Info("LoginAbort")
go func() {
defer s.panicHandler.HandlePanic()
s.loginAbort()
}()
return &emptypb.Empty{}, nil
}
func (s *Service) CheckUpdate(context.Context, *emptypb.Empty) (*emptypb.Empty, error) {
s.log.Info("CheckUpdate")
// TO-DO GODT-1670 Implement update check
return &emptypb.Empty{}, nil
}
func (s *Service) InstallUpdate(context.Context, *emptypb.Empty) (*emptypb.Empty, error) {
s.log.Info("InstallUpdate")
// TO-DO GODT-1670 Implement update install
return &emptypb.Empty{}, nil
}
func (s *Service) SetIsAutomaticUpdateOn(_ context.Context, isOn *wrapperspb.BoolValue) (*emptypb.Empty, error) {
s.log.WithField("isOn", isOn.Value).Info("SetIsAutomaticUpdateOn")
currentlyOn := s.settings.GetBool(settings.AutoUpdateKey)
if currentlyOn == isOn.Value {
return &emptypb.Empty{}, nil
}
s.settings.SetBool(settings.AutoUpdateKey, isOn.Value)
s.checkUpdateAndNotify()
return &emptypb.Empty{}, nil
}
func (s *Service) IsAutomaticUpdateOn(context.Context, *emptypb.Empty) (*wrapperspb.BoolValue, error) {
s.log.Info("IsAutomaticUpdateOn")
return wrapperspb.Bool(s.settings.GetBool(settings.AutoUpdateKey)), nil
}
func (s *Service) IsCacheOnDiskEnabled(context.Context, *emptypb.Empty) (*wrapperspb.BoolValue, error) {
s.log.Info("IsCacheOnDiskEnabled")
return wrapperspb.Bool(s.settings.GetBool(settings.CacheEnabledKey)), nil
}
func (s *Service) DiskCachePath(context.Context, *emptypb.Empty) (*wrapperspb.StringValue, error) {
s.log.Info("DiskCachePath")
return wrapperspb.String(s.settings.Get(settings.CacheLocationKey)), nil
}
func (s *Service) ChangeLocalCache(_ context.Context, change *ChangeLocalCacheRequest) (*emptypb.Empty, error) {
s.log.WithField("enableDiskCache", change.EnableDiskCache).
WithField("diskCachePath", change.DiskCachePath).
Info("DiskCachePath")
defer func() { _ = s.SendEvent(NewCacheChangeLocalCacheFinishedEvent()) }()
defer func() { _ = s.SendEvent(NewIsCacheOnDiskEnabledChanged(s.settings.GetBool(settings.CacheEnabledKey))) }()
defer func() { _ = s.SendEvent(NewDiskCachePathChanged(s.settings.Get(settings.CacheCompressionKey))) }()
if change.EnableDiskCache != s.settings.GetBool(settings.CacheEnabledKey) {
if change.EnableDiskCache {
if err := s.bridge.EnableCache(); err != nil {
s.log.WithError(err).Error("Cannot enable disk cache")
}
} else {
if err := s.bridge.DisableCache(); err != nil {
s.log.WithError(err).Error("Cannot disable disk cache")
}
}
}
path := change.DiskCachePath
//goland:noinspection GoBoolExpressions
if (runtime.GOOS == "windows") && (path[0] == '/') {
path = path[1:]
}
if change.EnableDiskCache && path != s.settings.Get(settings.CacheLocationKey) {
if err := s.bridge.MigrateCache(s.settings.Get(settings.CacheLocationKey), path); err != nil {
s.log.WithError(err).Error("The local cache location could not be changed.")
_ = s.SendEvent(NewCacheErrorEvent(CacheErrorType_CACHE_CANT_MOVE_ERROR))
return &emptypb.Empty{}, nil
}
s.settings.Set(settings.CacheLocationKey, path)
}
_ = s.SendEvent(NewCacheLocationChangeSuccessEvent())
s.restart()
return &emptypb.Empty{}, nil
}
func (s *Service) SetIsDoHEnabled(_ context.Context, isEnabled *wrapperspb.BoolValue) (*emptypb.Empty, error) {
s.log.WithField("isEnabled", isEnabled.Value).Info("SetIsDohEnabled")
s.bridge.SetProxyAllowed(isEnabled.Value)
return &emptypb.Empty{}, nil
}
func (s *Service) IsDoHEnabled(context.Context, *emptypb.Empty) (*wrapperspb.BoolValue, error) {
s.log.Info("IsDohEnabled")
return wrapperspb.Bool(s.bridge.GetProxyAllowed()), nil
}
func (s *Service) SetUseSslForSmtp(_ context.Context, useSsl *wrapperspb.BoolValue) (*emptypb.Empty, error) { //nolint:revive,stylecheck
s.log.WithField("useSsl", useSsl.Value).Info("SetUseSslForSmtp")
if s.settings.GetBool(settings.SMTPSSLKey) == useSsl.Value {
return &emptypb.Empty{}, nil
}
defer func() { _ = s.SendEvent(NewMailSettingsUseSslForSmtpFinishedEvent()) }()
s.settings.SetBool(settings.SMTPSSLKey, useSsl.Value)
s.restart()
return &emptypb.Empty{}, nil
}
func (s *Service) UseSslForSmtp(context.Context, *emptypb.Empty) (*wrapperspb.BoolValue, error) { //nolint:revive,stylecheck
s.log.Info("UseSslForSmtp")
return wrapperspb.Bool(s.settings.GetBool(settings.SMTPSSLKey)), nil
}
func (s *Service) Hostname(context.Context, *emptypb.Empty) (*wrapperspb.StringValue, error) {
s.log.Info("Hostname")
return wrapperspb.String(bridge.Host), nil
}
func (s *Service) ImapPort(context.Context, *emptypb.Empty) (*wrapperspb.Int32Value, error) {
s.log.Info("ImapPort")
return wrapperspb.Int32(int32(s.settings.GetInt(settings.IMAPPortKey))), nil
}
func (s *Service) SmtpPort(context.Context, *emptypb.Empty) (*wrapperspb.Int32Value, error) { //nolint:revive,stylecheck
s.log.Info("SmtpPort")
return wrapperspb.Int32(int32(s.settings.GetInt(settings.SMTPPortKey))), nil
}
func (s *Service) ChangePorts(_ context.Context, ports *ChangePortsRequest) (*emptypb.Empty, error) {
s.log.WithField("imapPort", ports.ImapPort).WithField("smtpPort", ports.SmtpPort).Info("ChangePorts")
defer func() { _ = s.SendEvent(NewMailSettingsChangePortFinishedEvent()) }()
s.settings.SetInt(settings.IMAPPortKey, int(ports.ImapPort))
s.settings.SetInt(settings.SMTPPortKey, int(ports.SmtpPort))
s.restart()
return &emptypb.Empty{}, nil
}
func (s *Service) IsPortFree(_ context.Context, port *wrapperspb.Int32Value) (*wrapperspb.BoolValue, error) {
s.log.Info("IsPortFree")
return wrapperspb.Bool(ports.IsPortFree(int(port.Value))), nil
}
func (s *Service) AvailableKeychains(context.Context, *emptypb.Empty) (*AvailableKeychainsResponse, error) {
s.log.Info("AvailableKeychains")
keychains := make([]string, 0, len(keychain.Helpers))
for chain := range keychain.Helpers {
keychains = append(keychains, chain)
}
return &AvailableKeychainsResponse{Keychains: keychains}, nil
}
func (s *Service) SetCurrentKeychain(_ context.Context, keychain *wrapperspb.StringValue) (*emptypb.Empty, error) {
s.log.WithField("keychain", keychain.Value).Info("SetCurrentKeyChain") // we do not check validity.
defer func() { _ = s.SendEvent(NewKeychainChangeKeychainFinishedEvent()) }()
if s.bridge.GetKeychainApp() == keychain.Value {
return &emptypb.Empty{}, nil
}
s.bridge.SetKeychainApp(keychain.Value)
return &emptypb.Empty{}, nil
}
func (s *Service) CurrentKeychain(context.Context, *emptypb.Empty) (*wrapperspb.StringValue, error) {
s.log.Info("CurrentKeychain")
return wrapperspb.String(s.bridge.GetKeychainApp()), nil
}

View File

@ -0,0 +1,151 @@
// Copyright (c) 2022 Proton AG
//
// This file is part of Proton Mail Bridge.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 grpc
import (
"context"
"github.com/pkg/errors"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/emptypb"
)
// StartEventStream implement the gRPC server->Client event stream.
func (s *Service) StartEventStream(_ *emptypb.Empty, server Bridge_StartEventStreamServer) error {
s.log.Info("Starting Event stream")
if s.eventStreamCh != nil {
return status.Errorf(codes.AlreadyExists, "the service is already streaming") // TO-DO GODT-1667 decide if we want to kill the existing stream.
}
s.eventStreamCh = make(chan *StreamEvent)
s.eventStreamDoneCh = make(chan struct{})
// TO-DO GODT-1667 We should have a safer we to close this channel? What if an event occur while we are closing?
defer func() {
close(s.eventStreamCh)
s.eventStreamCh = nil
close(s.eventStreamDoneCh)
s.eventStreamDoneCh = nil
}()
for {
select {
case <-s.eventStreamDoneCh:
s.log.Info("Stop Event stream")
return nil
case event := <-s.eventStreamCh:
s.log.WithField("event", event).Info("Sending event")
if err := server.Send(event); err != nil {
s.log.Info("Stop Event stream")
return err
}
}
}
}
// StopEventStream stops the event stream.
func (s *Service) StopEventStream(_ context.Context, _ *emptypb.Empty) (*emptypb.Empty, error) {
if s.eventStreamCh == nil {
return nil, status.Errorf(codes.NotFound, "The service is not streaming")
}
s.eventStreamDoneCh <- struct{}{}
return &emptypb.Empty{}, nil
}
// SendEvent sends an event to the via the gRPC event stream.
func (s *Service) SendEvent(event *StreamEvent) error {
if s.eventStreamCh == nil {
return errors.New("gRPC service is not streaming")
}
s.eventStreamCh <- event
return nil
}
// StartEventTest sends all the known event via gRPC.
func (s *Service) StartEventTest() error { //nolint:funlen
const dummyAddress = "dummy@proton.me"
events := []*StreamEvent{
// app
NewInternetStatusEvent(true),
NewToggleAutostartFinishedEvent(),
NewResetFinishedEvent(),
NewReportBugFinishedEvent(),
NewReportBugSuccessEvent(),
NewReportBugErrorEvent(),
NewShowMainWindowEvent(),
// login
NewLoginError(LoginErrorType_FREE_USER, "error"),
NewLoginTfaRequestedEvent(dummyAddress),
NewLoginTwoPasswordsRequestedEvent(),
NewLoginFinishedEvent("userID"),
NewLoginAlreadyLoggedInEvent("userID"),
// update
NewUpdateErrorEvent(UpdateErrorType_UPDATE_SILENT_ERROR),
NewUpdateManualReadyEvent("2.0"),
NewUpdateManualRestartNeededEvent(),
NewUpdateForceEvent("2.0"),
NewUpdateSilentRestartNeededEvent(),
NewUpdateIsLatestVersionEvent(),
NewUpdateCheckFinishedEvent(),
// cache
NewCacheErrorEvent(CacheErrorType_CACHE_UNAVAILABLE_ERROR),
NewCacheLocationChangeSuccessEvent(),
NewCacheChangeLocalCacheFinishedEvent(),
NewIsCacheOnDiskEnabledChanged(true),
NewDiskCachePathChanged("/dummy/path"),
// mail settings
NewMailSettingsErrorEvent(MailSettingsErrorType_IMAP_PORT_ISSUE),
NewMailSettingsUseSslForSmtpFinishedEvent(),
NewMailSettingsChangePortFinishedEvent(),
// keychain
NewKeychainChangeKeychainFinishedEvent(),
NewKeychainHasNoKeychainEvent(),
NewKeychainRebuildKeychainEvent(),
// mail
NewMailNoActiveKeyForRecipientEvent(dummyAddress),
NewMailAddressChangeEvent(dummyAddress),
NewMailAddressChangeLogoutEvent(dummyAddress),
NewMailApiCertIssue(),
// user
NewUserToggleSplitModeFinishedEvent("userID"),
NewUserDisconnectedEvent("username"),
NewUserChangedEvent("userID"),
}
for _, event := range events {
if err := s.SendEvent(event); err != nil {
return err
}
}
return nil
}

View File

@ -0,0 +1,132 @@
// 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 grpc
import (
"context"
"time"
"github.com/ProtonMail/proton-bridge/v2/internal/frontend/clientconfig"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/emptypb"
"google.golang.org/protobuf/types/known/wrapperspb"
)
func (s *Service) GetUserList(context.Context, *emptypb.Empty) (*UserListResponse, error) {
s.log.Info("GetUserList")
users := s.bridge.GetUsers()
userList := make([]*User, len(users))
for i, user := range users {
userList[i] = grpcUserFromBridge(user)
}
// If there are no active accounts.
if len(userList) == 0 {
s.log.Info("No active accounts")
}
return &UserListResponse{Users: userList}, nil
}
func (s *Service) GetUser(_ context.Context, userID *wrapperspb.StringValue) (*User, error) {
s.log.WithField("userID", userID).Info("GetUser")
user, err := s.bridge.GetUser(userID.Value)
if err != nil {
return nil, status.Errorf(codes.NotFound, "user not found %v", userID.Value)
}
return grpcUserFromBridge(user), nil
}
func (s *Service) SetUserSplitMode(_ context.Context, splitMode *UserSplitModeRequest) (*emptypb.Empty, error) {
s.log.WithField("UserID", splitMode.UserID).WithField("Active", splitMode.Active).Info("SetUserSplitMode")
user, err := s.bridge.GetUser(splitMode.UserID)
if err != nil {
return nil, status.Errorf(codes.NotFound, "user not found %v", splitMode.UserID)
}
go func() {
defer s.panicHandler.HandlePanic()
defer func() { _ = s.SendEvent(NewUserToggleSplitModeFinishedEvent(splitMode.UserID)) }()
if splitMode.Active == user.IsCombinedAddressMode() {
_ = user.SwitchAddressMode() // check for errors
}
}()
return &emptypb.Empty{}, nil
}
func (s *Service) LogoutUser(_ context.Context, userID *wrapperspb.StringValue) (*emptypb.Empty, error) {
s.log.WithField("UserID", userID.Value).Info("LogoutUser")
user, err := s.bridge.GetUser(userID.Value)
if err != nil {
return nil, status.Errorf(codes.NotFound, "user not found %v", userID.Value)
}
go func() {
defer s.panicHandler.HandlePanic()
_ = user.Logout()
}()
return &emptypb.Empty{}, nil
}
func (s *Service) RemoveUser(_ context.Context, userID *wrapperspb.StringValue) (*emptypb.Empty, error) {
s.log.WithField("UserID", userID.Value).Info("RemoveUser")
go func() {
defer s.panicHandler.HandlePanic()
// remove preferences
if err := s.bridge.DeleteUser(userID.Value, false); err != nil {
s.log.WithError(err).Error("Failed to remove user")
// notification
}
}()
return &emptypb.Empty{}, nil
}
func (s *Service) ConfigureUserAppleMail(_ context.Context, request *ConfigureAppleMailRequest) (*emptypb.Empty, error) {
s.log.WithField("UserID", request.UserID).WithField("Address", request.Address).Info("ConfigureUserAppleMail")
user, err := s.bridge.GetUser(request.UserID)
if err != nil {
s.log.WithField("userID", request.UserID).Error("Cannot configure AppleMail for user")
return nil, status.Error(codes.NotFound, "Cannot configure AppleMail for user")
}
needRestart, err := clientconfig.ConfigureAppleMail(user, request.Address, s.settings)
if err != nil {
s.log.WithError(err).Error("Apple Mail config failed")
return nil, status.Error(codes.Internal, "Apple Mail config failed")
}
if needRestart {
// There is delay needed for external window to open
time.Sleep(2 * time.Second)
s.restart()
}
return &emptypb.Empty{}, nil
}

View File

@ -15,29 +15,15 @@
// You should have received a copy of the GNU General Public License // 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/>. // along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
//go:build build_qt package grpc
// +build build_qt
package qt
import ( import (
"regexp" "regexp"
"strings" "strings"
"github.com/therecipe/qt/core" "github.com/ProtonMail/proton-bridge/v2/internal/frontend/types"
"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 ( var (
reMultiSpaces = regexp.MustCompile(`\s{2,}`) reMultiSpaces = regexp.MustCompile(`\s{2,}`)
reStartWithSymbol = regexp.MustCompile(`^[.,/#!$@%^&*;:{}=\-_` + "`" + `~()]`) reStartWithSymbol = regexp.MustCompile(`^[.,/#!$@%^&*;:{}=\-_` + "`" + `~()]`)
@ -69,3 +55,19 @@ func getInitials(fullName string) string {
} }
return strings.ToUpper(initials) return strings.ToUpper(initials)
} }
// grpcUserFromBridge converts a bridge user to a gRPC user.
func grpcUserFromBridge(user types.User) *User {
return &User{
Id: user.ID(),
Username: user.Username(),
AvatarText: getInitials(user.Username()),
LoggedIn: user.IsConnected(),
SplitMode: user.IsCombinedAddressMode(),
SetupGuideSeen: true, // users listed have already seen the setup guide.
UsedBytes: user.UsedBytes(),
TotalBytes: user.TotalBytes(),
Password: user.GetBridgePassword(),
Addresses: user.GetAddresses(),
}
}

View File

@ -1,162 +0,0 @@
// 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 provides communication between Qt/QML frontend and Go backend
package qt
import (
"fmt"
"sync"
"github.com/ProtonMail/proton-bridge/v2/internal/config/settings"
"github.com/ProtonMail/proton-bridge/v2/internal/config/useragent"
"github.com/ProtonMail/proton-bridge/v2/internal/frontend/types"
"github.com/ProtonMail/proton-bridge/v2/internal/locations"
"github.com/ProtonMail/proton-bridge/v2/internal/updater"
"github.com/ProtonMail/proton-bridge/v2/pkg/listener"
"github.com/ProtonMail/proton-bridge/v2/pkg/pmapi"
"github.com/sirupsen/logrus"
"github.com/therecipe/qt/core"
"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
restarter types.Restarter
showOnStartup bool
authClient pmapi.Client
auth *pmapi.Auth
password []byte
newVersionInfo updater.VersionInfo
log *logrus.Entry
initializing sync.WaitGroup
initializationDone sync.Once
firstTimeAutostart sync.Once
app *widgets.QApplication
engine *qml.QQmlEngine
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,
restarter types.Restarter,
) *FrontendQt {
userAgent.SetPlatform(core.QSysInfo_PrettyProductName())
f := &FrontendQt{
programName: programName,
programVersion: version,
log: logrus.WithField("pkg", "frontend/qt"),
panicHandler: panicHandler,
locations: locations,
settings: settings,
eventListener: eventListener,
updater: updater,
userAgent: userAgent,
bridge: bridge,
restarter: restarter,
showOnStartup: showWindowOnStart,
}
// Initializing.Done is only called sync.Once. Please keep the increment
// set to 1
f.initializing.Add(1)
return f
}
func (f *FrontendQt) Loop() error {
err := f.initiateQtApplication()
if err != nil {
return err
}
go func() {
defer f.panicHandler.HandlePanic()
f.watchEvents()
}()
// Set whether this is the first time GUI starts.
f.qml.SetIsFirstGUIStart(f.settings.GetBool(settings.FirstStartGUIKey))
defer func() {
f.settings.SetBool(settings.FirstStartGUIKey, false)
}()
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(core.NewQUrl3(version.ReleaseNotesPage, core.QUrl__TolerantMode))
f.qml.SetLandingPageLink(core.NewQUrl3(version.LandingPage, core.QUrl__TolerantMode))
}
func (f *FrontendQt) NotifySilentUpdateInstalled() {
f.qml.UpdateSilentRestartNeeded()
}
func (f *FrontendQt) NotifySilentUpdateError(err error) {
f.log.WithError(err).Warn("In-app update failed, asking for manual.")
f.qml.UpdateManualError()
}
func (f *FrontendQt) WaitUntilFrontendIsReady() {
f.initializing.Wait()
}

View File

@ -1,104 +0,0 @@
// 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 provides communication between Qt/QML frontend and Go backend
package qt
import (
"strings"
"github.com/ProtonMail/proton-bridge/v2/internal/bridge"
"github.com/ProtonMail/proton-bridge/v2/internal/events"
"github.com/ProtonMail/proton-bridge/v2/pkg/keychain"
)
func (f *FrontendQt) watchEvents() {
f.WaitUntilFrontendIsReady()
// First we check bridge global errors for any error that should be shown on GUI.
if f.bridge.HasError(bridge.ErrLocalCacheUnavailable) {
f.qml.CacheUnavailable()
}
errorCh := f.eventListener.ProvideChannel(events.ErrorEvent)
credentialsErrorCh := f.eventListener.ProvideChannel(events.CredentialsErrorEvent)
noActiveKeyForRecipientCh := f.eventListener.ProvideChannel(events.NoActiveKeyForRecipientEvent)
internetConnChangedCh := f.eventListener.ProvideChannel(events.InternetConnChangedEvent)
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)
// This loop is executed outside main Qt application thread. In order
// to make sure that all signals are propagated correctly to QML we
// must call QMLBackend signals to apply any changes to GUI. The
// signals will make sure the changes are executed in main Qt app
// thread.
for {
select {
case errorDetails := <-errorCh:
if strings.Contains(errorDetails, "IMAP failed") {
f.qml.PortIssueIMAP()
}
if strings.Contains(errorDetails, "SMTP failed") {
f.qml.PortIssueSMTP()
}
case reason := <-credentialsErrorCh:
if reason == keychain.ErrMacKeychainRebuild.Error() {
f.qml.NotifyRebuildKeychain()
continue
}
f.qml.NotifyHasNoKeychain()
case email := <-noActiveKeyForRecipientCh:
f.qml.NoActiveKeyForRecipient(email)
case stat := <-internetConnChangedCh:
if stat == events.InternetOff {
f.qml.InternetOff()
}
if stat == events.InternetOn {
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.qml.UserChanged(userID)
case <-certIssue:
f.qml.ApiCertIssue()
}
}
}

View File

@ -1,68 +0,0 @@
// 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 (
"github.com/therecipe/qt/core"
)
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(core.QUrl_FromLocalFile(path))
}
func (f *FrontendQt) setLicensePath() {
f.qml.SetLicensePath(core.QUrl_FromLocalFile(f.locations.GetLicenseFilePath()))
f.qml.SetDependencyLicensesLink(core.NewQUrl3(f.locations.GetDependencyLicensesLink(), core.QUrl__TolerantMode))
}
func (f *FrontendQt) setCurrentEmailClient() {
f.qml.SetCurrentEmailClient(f.userAgent.String())
}
func (f *FrontendQt) reportBug(description, address, emailClient string, includeLogs bool) {
defer f.qml.ReportBugFinished()
if err := f.bridge.ReportBug(
core.QSysInfo_ProductType(),
core.QSysInfo_PrettyProductName(),
description,
address,
address,
emailClient,
includeLogs,
); err != nil {
f.log.WithError(err).Error("Failed to report bug")
f.qml.BugReportSendError()
return
}
f.qml.BugReportSendSuccess()
}

View File

@ -1,120 +0,0 @@
// 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 (
"errors"
"os"
"runtime"
"github.com/Masterminds/semver/v3"
qmlLog "github.com/ProtonMail/proton-bridge/v2/internal/frontend/qt/log"
"github.com/therecipe/qt/core"
"github.com/therecipe/qt/qml"
"github.com/therecipe/qt/quickcontrols2"
"github.com/therecipe/qt/widgets"
)
func (f *FrontendQt) initiateQtApplication() error {
qmlLog.InstallMessageHandler()
f.app = widgets.NewQApplication(len(os.Args), os.Args)
if os.Getenv("QSG_INFO") != "" && os.Getenv("QSG_INFO") != "0" {
core.QLoggingCategory_SetFilterRules("qt.scenegraph.general=true")
}
core.QCoreApplication_SetApplicationName(f.programName)
core.QCoreApplication_SetApplicationVersion(f.programVersion)
core.QCoreApplication_SetOrganizationName("Proton AG")
core.QCoreApplication_SetOrganizationDomain("proton.ch")
// High DPI scaling for windows.
core.QCoreApplication_SetAttribute(core.Qt__AA_EnableHighDpiScaling, false)
// Use software OpenGL to avoid dedicated GPU on darwin. It cause no
// problems on linux, but it can cause initializaion issues on windows
// for some specific GPU / driver combination.
if runtime.GOOS != "windows" {
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.NewQQmlEngine(f.app)
rootComponent := qml.NewQQmlComponent2(f.engine, f.engine)
f.qml = NewQMLBackend(f.engine)
f.qml.setup(f)
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")
// Before loading a component we should load translations.
// See https://github.com/qt/qtdeclarative/blob/bedef212a74a62452ed31d7f65536a6c67881fc4/src/qml/qml/qqmlapplicationengine.cpp#L69 as example.
rootComponent.LoadUrl(core.NewQUrl3("qrc:/qml/Bridge.qml", 0))
if rootComponent.Status() != qml.QQmlComponent__Ready {
return errors.New("QML not loaded properly")
}
// Instead of creating component right away we use BeginCreate to stop right before binding evaluation.
// That is needed to set backend property so all bindings will be calculated properly.
rootObject := rootComponent.BeginCreate(f.engine.RootContext())
// Check QML is loaded properly.
if rootObject == nil {
return errors.New("QML not created properly")
}
rootObject.SetProperty("backend", f.qml.ToVariant())
rootComponent.CompleteCreate()
return nil
}
func (f *FrontendQt) setShowSplashScreen() {
f.qml.SetShowSplashScreen(false)
// Splash screen should not be shown to new users or after factory reset.
if f.bridge.IsFirstStart() {
return
}
ver, err := semver.NewVersion(f.bridge.GetLastVersion())
if err != nil {
f.log.WithError(err).WithField("last", f.bridge.GetLastVersion()).Debug("Cannot parse last version")
return
}
// Current splash screen contains update on rebranding. Therefore, it
// should be shown only if the last used version was less than 2.2.0.
if ver.LessThan(semver.MustParse("2.2.0")) {
f.qml.SetShowSplashScreen(true)
}
}

View File

@ -1,82 +0,0 @@
// 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 (
"fmt"
"net/http"
"github.com/ProtonMail/proton-bridge/v2/internal/config/settings"
"github.com/ProtonMail/proton-bridge/v2/internal/config/useragent"
"github.com/ProtonMail/proton-bridge/v2/internal/frontend/types"
"github.com/ProtonMail/proton-bridge/v2/internal/locations"
"github.com/ProtonMail/proton-bridge/v2/internal/updater"
"github.com/ProtonMail/proton-bridge/v2/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,
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

@ -1,218 +0,0 @@
// 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 (
"runtime"
"time"
"github.com/ProtonMail/proton-bridge/v2/internal/config/settings"
"github.com/ProtonMail/proton-bridge/v2/internal/frontend/clientconfig"
"github.com/ProtonMail/proton-bridge/v2/internal/frontend/theme"
"github.com/ProtonMail/proton-bridge/v2/pkg/keychain"
"github.com/ProtonMail/proton-bridge/v2/pkg/ports"
"github.com/therecipe/qt/core"
)
func (f *FrontendQt) setIsDiskCacheEnabled() {
f.qml.SetIsDiskCacheEnabled(f.settings.GetBool(settings.CacheEnabledKey))
}
func (f *FrontendQt) setDiskCachePath() {
f.qml.SetDiskCachePath(core.QUrl_FromLocalFile(f.settings.Get(settings.CacheLocationKey)))
}
func (f *FrontendQt) changeLocalCache(enableDiskCache bool, diskCachePath *core.QUrl) {
defer f.qml.ChangeLocalCacheFinished()
defer f.setIsDiskCacheEnabled()
defer f.setDiskCachePath()
if enableDiskCache != f.settings.GetBool(settings.CacheEnabledKey) {
if enableDiskCache {
if err := f.bridge.EnableCache(); err != nil {
f.log.WithError(err).Error("Cannot enable disk cache")
}
} else {
if err := f.bridge.DisableCache(); err != nil {
f.log.WithError(err).Error("Cannot disable disk cache")
}
}
}
_diskCachePath := diskCachePath.Path(core.QUrl__FullyDecoded)
if (runtime.GOOS == "windows") && (_diskCachePath[0] == '/') {
_diskCachePath = _diskCachePath[1:]
}
if enableDiskCache && _diskCachePath != f.settings.Get(settings.CacheLocationKey) {
if err := f.bridge.MigrateCache(f.settings.Get(settings.CacheLocationKey), _diskCachePath); err != nil {
f.log.WithError(err).Error("The local cache location could not be changed.")
f.qml.CacheCantMove()
return
}
f.settings.Set(settings.CacheLocationKey, _diskCachePath)
}
f.qml.CacheLocationChangeSuccess()
f.restart()
}
func (f *FrontendQt) setIsAutostartOn() {
// GODT-1507 Windows: autostart needs to be created after Qt is initialized.
// GODT-1206: if preferences file says it should be on enable it here.
f.firstTimeAutostart.Do(func() {
shouldAutostartBeOn := f.settings.GetBool(settings.AutostartKey)
if f.bridge.IsFirstStart() || shouldAutostartBeOn {
if err := f.bridge.EnableAutostart(); err != nil {
f.log.WithField("prefs", shouldAutostartBeOn).WithError(err).Error("Failed to enable first autostart")
}
return
}
})
f.qml.SetIsAutostartOn(f.bridge.IsAutostartEnabled())
}
func (f *FrontendQt) toggleAutostart(makeItEnabled bool) {
defer f.qml.ToggleAutostartFinished()
if makeItEnabled == f.bridge.IsAutostartEnabled() {
f.setIsAutostartOn()
return
}
var err error
if makeItEnabled {
err = f.bridge.EnableAutostart()
} else {
err = f.bridge.DisableAutostart()
}
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) {
f.bridge.SetProxyAllowed(makeItEnabled)
f.qml.SetIsDoHEnabled(f.bridge.GetProxyAllowed())
}
func (f *FrontendQt) toggleUseSSLforSMTP(makeItEnabled bool) {
if f.settings.GetBool(settings.SMTPSSLKey) == makeItEnabled {
f.qml.SetUseSSLforSMTP(makeItEnabled)
return
}
f.settings.SetBool(settings.SMTPSSLKey, makeItEnabled)
f.restart()
}
func (f *FrontendQt) changePorts(imapPort, smtpPort int) {
defer f.qml.ChangePortFinished()
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()
f.bridge.FactoryReset()
f.restart()
}
func (f *FrontendQt) setKeychain() {
availableKeychain := []string{}
for chain := range keychain.Helpers {
availableKeychain = append(availableKeychain, chain)
}
f.qml.SetAvailableKeychain(availableKeychain)
f.qml.SetCurrentKeychain(f.bridge.GetKeychainApp())
}
func (f *FrontendQt) changeKeychain(wantKeychain string) {
defer f.qml.ChangeKeychainFinished()
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)
}
func (f *FrontendQt) guiReady() {
f.initializationDone.Do(f.initializing.Done)
}
func (f *FrontendQt) setColorScheme() {
current := f.settings.Get(settings.ColorScheme)
if !theme.IsAvailable(theme.Theme(current)) {
current = string(theme.DefaultTheme())
f.settings.Set(settings.ColorScheme, current)
}
f.qml.SetColorSchemeName(current)
}
func (f *FrontendQt) changeColorScheme(newScheme string) {
if !theme.IsAvailable(theme.Theme(newScheme)) {
f.log.WithField("scheme", newScheme).Warn("Color scheme not available")
return
}
f.settings.Set(settings.ColorScheme, newScheme)
f.setColorScheme()
}

View File

@ -1,148 +0,0 @@
// 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/config/settings"
"github.com/ProtonMail/proton-bridge/v2/internal/updater"
"github.com/pkg/errors"
)
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.Unlock()
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.UpdateManualError()
return
}
if f.settings.GetBool(settings.AutoUpdateKey) {
// NOOP will update eventually
return
}
if isRequestFromUser {
f.qml.UpdateManualReady(f.newVersionInfo.Version.String())
}
}
func (f *FrontendQt) updateForce() {
checkingUpdates.Lock()
defer checkingUpdates.Unlock()
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 := updater.StableChannel
if makeItEnabled {
channel = updater.EarlyChannel
}
f.bridge.SetUpdateChannel(channel)
f.setIsBetaEnabled()
// Immediately check the updates to set the correct landing page link.
f.checkUpdates()
}
func (f *FrontendQt) installUpdate() {
checkingUpdates.Lock()
defer checkingUpdates.Unlock()
if !f.updater.CanInstall(f.newVersionInfo) {
f.log.Warning("Skipping update installation, current version too old")
f.qml.UpdateManualError()
return
}
if err := f.updater.InstallUpdate(f.newVersionInfo); err != nil {
if errors.Cause(err) == updater.ErrDownloadVerify {
f.log.WithError(err).Warning("Skipping update installation due to temporary error")
} else {
f.log.WithError(err).Error("The update couldn't be installed")
f.qml.UpdateManualError()
}
return
}
f.qml.UpdateSilentRestartNeeded()
}

View File

@ -1,196 +0,0 @@
// 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 (
"context"
"encoding/base64"
"time"
"github.com/ProtonMail/proton-bridge/v2/internal/events"
"github.com/ProtonMail/proton-bridge/v2/internal/users"
"github.com/ProtonMail/proton-bridge/v2/pkg/pmapi"
)
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 {
if err == pmapi.ErrPasswordWrong {
// Remove error message since it is hardcodded in QML.
f.qml.LoginUsernamePasswordError("")
f.loginClean()
return
}
if err == pmapi.ErrPaidPlanRequired {
f.qml.LoginFreeUserError()
f.loginClean()
return
}
f.qml.LoginUsernamePasswordError(err.Error())
f.loginClean()
return
}
if f.auth.HasTwoFactor() {
f.qml.Login2FARequested(username)
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 len(f.password) == 0 || f.auth == nil || f.authClient == nil {
f.log.
WithField("hasPass", len(f.password) != 0).
WithField("hasAuth", f.auth != nil).
WithField("hasClient", f.authClient != nil).
Error("Finish login: authethication incomplete")
f.qml.Login2PasswordErrorAbort("Missing authentication, try again.")
return
}
done := make(chan string)
f.eventListener.Add(events.UserChangeDone, done)
defer f.eventListener.Remove(events.UserChangeDone, done)
user, err := f.bridge.FinishLogin(f.authClient, f.auth, f.password)
if err != nil && err != users.ErrUserAlreadyConnected {
f.log.WithError(err).Errorf("Finish login failed")
f.qml.Login2PasswordErrorAbort(err.Error())
return
}
// The user changed should be triggerd by FinishLogin but it is not
// guaranteed when this is going to happen. Therefore we should wait
// until we receive the signal from userChanged function.
f.waitForUserChangeDone(done, user.ID())
index := f.qml.Users().indexByID(user.ID())
f.log.WithField("index", index).Debug("Login finished")
defer f.qml.LoginFinished(index)
if err == users.ErrUserAlreadyConnected {
f.log.WithError(err).Error("User already logged in")
f.qml.LoginAlreadyLoggedIn(index)
}
}
func (f *FrontendQt) waitForUserChangeDone(done <-chan string, userID string) {
for {
select {
case changedID := <-done:
if changedID == userID {
return
}
case <-time.After(2 * time.Second):
f.log.WithField("ID", userID).Warning("Login finished but user not added within 2 seconds")
return
}
}
}
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

@ -1,44 +0,0 @@
// 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/>.
// +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

@ -1,47 +0,0 @@
// 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 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

@ -1,317 +0,0 @@
// 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 (
"runtime"
"github.com/ProtonMail/proton-bridge/v2/internal/bridge"
"github.com/ProtonMail/proton-bridge/v2/internal/config/settings"
dockIcon "github.com/ProtonMail/proton-bridge/v2/internal/frontend/qt/dockicon"
"github.com/therecipe/qt/core"
)
func init() {
QMLBackend_QRegisterMetaType()
}
// QMLBackend connects QML frontend with Go backend.
type QMLBackend struct {
core.QObject
_ func() *core.QPoint `slot:"getCursorPos"`
_ func() `slot:"guiReady"`
_ func() `slot:"quit"`
_ func() `slot:"restart"`
_ bool `property:showOnStartup`
_ bool `property:showSplashScreen`
_ 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() `signal:"loginFreeUserError"`
_ func(errorMsg string) `signal:"loginConnectionError"`
_ func(username string) `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(index int) `signal:"loginFinished"`
_ func(index int) `signal:"loginAlreadyLoggedIn"`
_ 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"`
_ func() `slot:"installUpdate"`
_ bool `property:"isDiskCacheEnabled"`
_ core.QUrl `property:"diskCachePath"`
_ func() `signal:"cacheUnavailable"`
_ func() `signal:"cacheCantMove"`
_ func() `signal:"cacheLocationChangeSuccess"`
_ func() `signal:"diskFull"`
_ func(enableDiskCache bool, diskCachePath core.QUrl) `slot:"changeLocalCache"`
_ func() `signal:"changeLocalCacheFinished"`
_ bool `property:"isFirstGUIStart"`
_ 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"`
_ core.QUrl `property:"logsPath"`
_ core.QUrl `property:"licensePath"`
_ core.QUrl `property:"releaseNotesLink"`
_ core.QUrl `property:"dependencyLicensesLink"`
_ core.QUrl `property:"landingPageLink"`
_ string `property:"colorSchemeName"`
_ func(string) `slot:"changeColorScheme"`
_ 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:"currentKeychain"`
_ func(keychain string) `slot:"changeKeychain"`
_ func() `signal:"changeKeychainFinished"`
_ func() `signal:"notifyHasNoKeychain"`
_ func() `signal:"notifyRebuildKeychain"`
_ 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(userID string) `signal:userChanged`
_ bool `property:"isAllMailVisible"`
_ func(isDisabled bool) `slot:"changeIsAllMailVisible"`
}
func (q *QMLBackend) setup(f *FrontendQt) {
q.ConnectGetCursorPos(getCursorPos)
q.ConnectQuit(f.quit)
q.ConnectRestart(f.restart)
q.ConnectGuiReady(f.guiReady)
q.ConnectIsShowOnStartup(func() bool {
return f.showOnStartup
})
q.ConnectIsDockIconVisible(dockIcon.GetDockIconVisibleState)
q.ConnectSetDockIconVisible(dockIcon.SetDockIconVisibleState)
um := NewQMLUserModel(q)
um.f = f
q.SetUsers(um)
um.load()
q.ConnectUserChanged(um.userChanged)
q.SetGoos(runtime.GOOS)
q.ConnectLogin(func(u, p string) {
go func() {
defer f.panicHandler.HandlePanic()
f.login(u, p)
}()
})
q.ConnectLogin2FA(func(u, p string) {
go func() {
defer f.panicHandler.HandlePanic()
f.login2FA(u, p)
}()
})
q.ConnectLogin2Password(func(u, p string) {
go func() {
defer f.panicHandler.HandlePanic()
f.login2Password(u, p)
}()
})
q.ConnectLoginAbort(func(u string) {
go func() {
defer f.panicHandler.HandlePanic()
f.loginAbort(u)
}()
})
go func() {
defer f.panicHandler.HandlePanic()
f.checkUpdatesAndNotify(false)
}()
q.ConnectCheckUpdates(func() {
go func() {
defer f.panicHandler.HandlePanic()
f.checkUpdatesAndNotify(true)
}()
})
q.ConnectInstallUpdate(func() {
go func() {
defer f.panicHandler.HandlePanic()
f.installUpdate()
}()
})
f.setIsDiskCacheEnabled()
f.setDiskCachePath()
q.ConnectChangeLocalCache(func(e bool, d *core.QUrl) {
go func() {
defer f.panicHandler.HandlePanic()
f.changeLocalCache(e, d)
}()
})
f.setIsAutomaticUpdateOn()
q.ConnectToggleAutomaticUpdate(func(m bool) {
go func() {
defer f.panicHandler.HandlePanic()
f.toggleAutomaticUpdate(m)
}()
})
f.setIsAutostartOn()
q.ConnectToggleAutostart(f.toggleAutostart)
f.setIsBetaEnabled()
q.ConnectToggleBeta(func(m bool) {
go func() {
defer f.panicHandler.HandlePanic()
f.toggleBeta(m)
}()
})
q.SetIsDoHEnabled(f.bridge.GetProxyAllowed())
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 func() {
defer f.panicHandler.HandlePanic()
f.triggerReset()
}()
})
f.setShowSplashScreen()
f.setVersion()
f.setLogsPath()
// release notes link is set by update
f.setLicensePath()
f.setColorScheme()
q.ConnectChangeColorScheme(func(newScheme string) {
go func() {
defer f.panicHandler.HandlePanic()
f.changeColorScheme(newScheme)
}()
})
f.setCurrentEmailClient()
q.ConnectUpdateCurrentMailClient(func() {
go func() {
defer f.panicHandler.HandlePanic()
f.setCurrentEmailClient()
}()
})
q.ConnectReportBug(func(d, a, e string, i bool) {
go func() {
defer f.panicHandler.HandlePanic()
f.reportBug(d, a, e, i)
}()
})
f.setKeychain()
q.ConnectChangeKeychain(func(k string) {
go func() {
defer f.panicHandler.HandlePanic()
f.changeKeychain(k)
}()
})
q.SetIsAllMailVisible(f.bridge.IsAllMailVisible())
q.ConnectChangeIsAllMailVisible(func(isVisible bool) {
f.bridge.SetIsAllMailVisible(isVisible)
f.qml.SetIsAllMailVisible(isVisible)
})
}

View File

@ -1,297 +0,0 @@
// 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())
}

View File

@ -0,0 +1,45 @@
// 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/>.
#include "Pch.h"
#include "AppController.h"
#include "QMLBackend.h"
#include "GRPC/GRPCClient.h"
#include "Log.h"
//****************************************************************************************************************************************************
/// \return The AppController instance.
//****************************************************************************************************************************************************
AppController &app()
{
static AppController app;
return app;
}
//****************************************************************************************************************************************************
//
//****************************************************************************************************************************************************
AppController::AppController()
: backend_(std::make_unique<QMLBackend>())
, grpc_(std::make_unique<GRPCClient>())
, log_(std::make_unique<Log>())
{
}

View File

@ -0,0 +1,59 @@
// 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/>.
#ifndef BRIDGE_QT6_APP_CONTROLLER_H
#define BRIDGE_QT6_APP_CONTROLLER_H
class QMLBackend;
class GRPCClient;
class Log;
//****************************************************************************************************************************************************
/// \brief App controller class.
//****************************************************************************************************************************************************
class AppController: public QObject
{
Q_OBJECT
friend AppController& app();
public: // member functions.
AppController(AppController const&) = delete; ///< Disabled copy-constructor.
AppController(AppController&&) = delete; ///< Disabled assignment copy-constructor.
~AppController() override = default; ///< Destructor.
AppController& operator=(AppController const&) = delete; ///< Disabled assignment operator.
AppController& operator=(AppController&&) = delete; ///< Disabled move assignment operator.
QMLBackend& backend() { return *backend_; } ///< Return a reference to the backend.
GRPCClient& grpc() { return *grpc_; } ///< Return a reference to the GRPC client.
Log& log() { return *log_; } ///< Return a reference to the log.
private: // member functions
AppController(); ///< Default constructor.
private: // data members
std::unique_ptr<QMLBackend> backend_; ///< The backend.
std::unique_ptr<GRPCClient> grpc_; ///< The RPC client.
std::unique_ptr<Log> log_; ///< The log.
};
AppController& app(); ///< Return a reference to the app controller.
#endif // BRIDGE_QT6_APP_CONTROLLER_H

View File

@ -0,0 +1,136 @@
# 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/>.
cmake_minimum_required(VERSION 3.22)
set(CMAKE_OSX_ARCHITECTURES x86_64) # needs to be set before the first project() directive.
#We rely on vcpkg for to get gRPC+Protobuf
if (NOT DEFINED ENV{VCPKG_ROOT})
message(FATAL_ERROR "vcpkg is required. Install vcpkg and define VCPKG_ROOT to point the the vcpkg installation folder. (e.g. ~/vcpkg/")
endif()
if (APPLE)
set(VCPKG_TARGET_TRIPLET x64-osx)
endif()
if (WIN32)
set(VCPKG_TARGET_TRIPLET x64-mingw-static)
endif()
set(CMAKE_TOOLCHAIN_FILE "$ENV{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake" CACHE STRING "toolchain")
project(bridge_qt6 LANGUAGES CXX)
if (APPLE) # On macOS, we have some Objective-C++ code in DockIcon to deal with ... the dock icon.
enable_language(OBJC OBJCXX)
endif()
if (NOT DEFINED ENV{QT5DIR})
message(FATAL_ERROR "QT5DIR needs to be defined and point to the root of your Qt5 folder (e.g. /Users/MyName/Qt/5.10.1/clang_64).")
endif()
set(CMAKE_PREFIX_PATH $ENV{QT5DIR} ${CMAKE_PREFIX_PATH})
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_AUTOUIC ON)
find_package(Protobuf CONFIG REQUIRED)
message(STATUS "Using protobuf ${Protobuf_VERSION}")
find_package(grpc CONFIG REQUIRED)
message(STATUS "Using gRPC ${gRPC_VERSION}")
if (APPLE) # We need to link the Cocoa framework for the dock icon.
find_library(COCOA_LIBRARY Cocoa REQUIRED)
endif()
find_package(Qt5 COMPONENTS
Core
Quick
Qml
QuickControls2
REQUIRED)
find_program(PROTOC_EXE protoc)
find_program(GRPC_CPP_PLUGIN grpc_cpp_plugin)
set(PROTO_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../grpc")
set(PROTO_FILE "${PROTO_DIR}/bridge.proto")
set(GRPC_OUT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/GRPC")
set(PROTO_CPP_FILE "${GRPC_OUT_DIR}/bridge.pb.cc")
set(PROTO_H_FILE "${GRPC_OUT_DIR}/bridge.pb.h")
set(GRPC_CPP_FILE "${GRPC_OUT_DIR}/bridge.grpc.pb.cc")
set(GRPC_H_FILE "${GRPC_OUT_DIR}/bridge.grpc.pb.h")
if (APPLE)
set(DOCK_ICON_SRC_FILE DockIcon/DockIcon.mm)
else()
set(DOCK_ICON_SRC_FILE DockIcon/DockIcon.cpp)
endif()
add_custom_command(
OUTPUT
${PROTO_CPP_FILE}
${PROTO_H_FILE}
${GRPC_CPP_FILE}
${GRPC_H_FILE}
COMMAND
${PROTOC_EXE}
ARGS
--proto_path=${PROTO_DIR}
--plugin=protoc-gen-grpc="${GRPC_CPP_PLUGIN}"
--cpp_out=${GRPC_OUT_DIR}
--grpc_out=${GRPC_OUT_DIR}
${PROTO_FILE}
DEPENDS
${PROTO_FILE}
COMMENT "Generating gPRC/Protobuf C++ code"
)
add_executable(bridge_qt6
${PROTO_CPP_FILE} ${PROTO_H_FILE} ${GRPC_CPP_FILE} ${GRPC_H_FILE}
AppController.cpp AppController.h
EventStreamWorker.cpp EventStreamWorker.h
Exception.cpp Exception.h
Log.cpp Log.h
main.cpp
Pch.h
QMLBackend.cpp QMLBackend.h
${DOCK_ICON_SRC_FILE} DockIcon/DockIcon.h
GRPC/GRPCClient.cpp GRPC/GRPCClient.h
GRPC/GRPCUtils.cpp GRPC/GRPCUtils.h
User/User.cpp User/User.h User/UserList.cpp User/UserList.h
Worker/Overseer.cpp Worker/Overseer.h
Worker/Worker.h)
include_directories(${CMAKE_CURRENT_SOURCE_DIR})
target_precompile_headers(bridge_qt6 PRIVATE Pch.h)
target_link_libraries(bridge_qt6
Qt5::Core
Qt5::Quick
Qt5::Qml
Qt5::QuickControls2
protobuf::libprotobuf
gRPC::grpc++
)
if (APPLE)
target_link_libraries(bridge_qt6 ${COCOA_LIBRARY})
endif()

View File

@ -15,13 +15,15 @@
// You should have received a copy of the GNU General Public License // 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/>. // along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
//go:build !darwin && build_qt
// +build !darwin,build_qt
package dockicon #include "Pch.h"
func SetDockIconVisibleState(visible bool) {}
func GetDockIconVisibleState() bool { #ifndef Q_OS_MACOS
return true
}
void setDockIconVisibleState(bool visible) { Q_UNUSED(visible) }
bool getDockIconVisibleState() { return true; }
#endif

View File

@ -15,22 +15,13 @@
// You should have received a copy of the GNU General Public License // 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/>. // along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
#pragma once
#ifndef LOGRUS_QML_LOG_H #ifndef BRIDGE_QT6_DOCK_ICON_H
#define LOGRUS_QML_LOG_H #define BRIDGE_QT6_DOCK_ICON_H
#include <stdint.h>
#ifdef __cplusplus void setDockIconVisibleState(bool visible); ///< Set the DOCK icon visibility state
extern "C" { bool getDockIconVisibleState(); ///< Get the Dock icon visibility state
#endif // C++
void InstallMessageHandler();
;
#ifdef __cplusplus #endif // #ifndef BRIDGE_QT6_DOCK_ICON_H
}
#endif // C++
#endif // LOGRUS_QML_LOG_H

View File

@ -15,13 +15,16 @@
// You should have received a copy of the GNU General Public License // 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/>. // along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
// +build darwin
// +build build_qt
#include "DockIcon.h" #include "Pch.h"
#include <Cocoa/Cocoa.h> #include <Cocoa/Cocoa.h>
#include "DockIcon.h"
void SetDockIconVisibleState(bool visible) {
#ifdef Q_OS_MACOS
void setDockIconVisibleState(bool visible) {
if (visible) { if (visible) {
[NSApp setActivationPolicy: NSApplicationActivationPolicyRegular]; [NSApp setActivationPolicy: NSApplicationActivationPolicyRegular];
return; return;
@ -31,12 +34,16 @@ void SetDockIconVisibleState(bool visible) {
} }
} }
bool GetDockIconVisibleState() {
bool getDockIconVisibleState() {
switch ([NSApp activationPolicy]) { switch ([NSApp activationPolicy]) {
case NSApplicationActivationPolicyAccessory: case NSApplicationActivationPolicyAccessory:
case NSApplicationActivationPolicyProhibited: case NSApplicationActivationPolicyProhibited:
return false; return false;
case NSApplicationActivationPolicyRegular: case NSApplicationActivationPolicyRegular:
return true; return true;
} }
} }
#endif // #ifdef Q_OS_MACOS

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,57 @@
// 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/>.
#include "Pch.h"
#include "EventStreamWorker.h"
#include "GRPC/GRPCClient.h"
#include "Log.h"
#include "Exception.h"
//****************************************************************************************************************************************************
/// \param[in] parent The parent object.
//****************************************************************************************************************************************************
EventStreamReader::EventStreamReader(QObject *parent)
: Worker(parent)
{
connect(this, &EventStreamReader::started, [&]() { app().log().debug("EventStreamReader started");});
connect(this, &EventStreamReader::finished, [&]() { app().log().debug("EventStreamReader finished");});
connect(this, &EventStreamReader::error, &app().log(), &Log::error);
}
//****************************************************************************************************************************************************
//
//****************************************************************************************************************************************************
void EventStreamReader::run()
{
try
{
emit started();
grpc::Status const status = app().grpc().startEventStream();
if (!status.ok())
throw Exception(QString::fromStdString(status.error_message()));
emit finished();
}
catch (Exception const &e)
{
emit error(e.qwhat());
}
}

View File

@ -0,0 +1,52 @@
// 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/>.
#ifndef BRIDGE_QT6_EVENT_STREAM_WORKER_H
#define BRIDGE_QT6_EVENT_STREAM_WORKER_H
#include "GRPC/bridge.grpc.pb.h"
#include "Worker/Worker.h"
//****************************************************************************************************************************************************
/// \brief Stream reader class.
//****************************************************************************************************************************************************
class EventStreamReader: public Worker
{
Q_OBJECT
public: // member functions
explicit EventStreamReader(QObject *parent); ///< Default constructor.
EventStreamReader(EventStreamReader const&) = delete; ///< Disabled copy-constructor.
EventStreamReader(EventStreamReader&&) = delete; ///< Disabled assignment copy-constructor.
~EventStreamReader() override = default; ///< Destructor.
EventStreamReader& operator=(EventStreamReader const&) = delete; ///< Disabled assignment operator.
EventStreamReader& operator=(EventStreamReader&&) = delete; ///< Disabled move assignment operator.
public slots:
void run() override; ///< Run the reader.
signals:
#pragma clang diagnostic push
#pragma ide diagnostic ignored "NotImplementedFunctions"
void eventReceived(QString eventString); ///< signal for events.
#pragma clang diagnostic pop
};
#endif //BRIDGE_QT6_EVENT_STREAM_WORKER_H

View File

@ -0,0 +1,68 @@
// 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/>.
#include "Pch.h"
#include "Exception.h"
//****************************************************************************************************************************************************
/// \param[in] what A description of the exception
//****************************************************************************************************************************************************
Exception::Exception(QString what) noexcept
: std::exception()
, what_(std::move(what))
{
}
//****************************************************************************************************************************************************
/// \param[in] ref The Exception to copy from
//****************************************************************************************************************************************************
Exception::Exception(Exception const& ref) noexcept
: std::exception(ref)
, what_(ref.what_)
{
}
//****************************************************************************************************************************************************
/// \param[in] ref The Exception to copy from
//****************************************************************************************************************************************************
Exception::Exception(Exception&& ref) noexcept
: std::exception(ref)
, what_(ref.what_)
{
}
//****************************************************************************************************************************************************
/// \return a string describing the exception
//****************************************************************************************************************************************************
QString const& Exception::qwhat() const noexcept
{
return what_;
}
//****************************************************************************************************************************************************
/// \return A pointer to the description string of the exception.
//****************************************************************************************************************************************************
const char* Exception::what() const noexcept
{
return what_.toLocal8Bit().constData();
}

View File

@ -0,0 +1,46 @@
// 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/>.
#ifndef BRIDGE_QT6_EXCEPTION_H
#define BRIDGE_QT6_EXCEPTION_H
#include <stdexcept>
//****************************************************************************************************************************************************
/// \brief Exception class.
//****************************************************************************************************************************************************
class Exception: public std::exception
{
public: // member functions
explicit Exception(QString what = QString()) noexcept; ///< Constructor
Exception(Exception const& ref) noexcept; ///< copy constructor
Exception(Exception&& ref) noexcept; ///< copy constructor
Exception& operator=(Exception const&) = delete; ///< Disabled assignment operator
Exception& operator=(Exception&&) = delete; ///< Disabled assignment operator
~Exception() noexcept override = default; ///< Destructor
QString const& qwhat() const noexcept; ///< Return the description of the exception as a QString
const char* what() const noexcept override; ///< Return the description of the exception as C style string
private: // data members
QString const what_; ///< The description of the exception
};
#endif //BRIDGE_QT6_EXCEPTION_H

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,214 @@
// 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/>.
#ifndef BRIDGE_QT6_RPC_CLIENT_H
#define BRIDGE_QT6_RPC_CLIENT_H
#include "GRPC/bridge.grpc.pb.h"
#include "grpc++/grpc++.h"
#include "User/User.h"
typedef grpc::Status (grpc::Bridge::Stub::*SimpleMethod)(grpc::ClientContext*, const google::protobuf::Empty&, google::protobuf::Empty*);
typedef grpc::Status (grpc::Bridge::Stub::*BoolSetter)(grpc::ClientContext*, const google::protobuf::BoolValue&, google::protobuf::Empty*);
typedef grpc::Status (grpc::Bridge::Stub::*BoolGetter)(grpc::ClientContext*, const google::protobuf::Empty&, google::protobuf::BoolValue*);
typedef grpc::Status (grpc::Bridge::Stub::*Int32Setter)(grpc::ClientContext*, const google::protobuf::Int32Value&, google::protobuf::Empty*);
typedef grpc::Status (grpc::Bridge::Stub::*Int32Getter)(grpc::ClientContext*, const google::protobuf::Empty&, google::protobuf::Int32Value*);
typedef grpc::Status (grpc::Bridge::Stub::*StringGetter)(grpc::ClientContext*, const google::protobuf::Empty&, google::protobuf::StringValue*);
typedef grpc::Status (grpc::Bridge::Stub::*StringSetter)(grpc::ClientContext*, const google::protobuf::StringValue&, google::protobuf::Empty*);
typedef grpc::Status (grpc::Bridge::Stub::*StringParamMethod)(grpc::ClientContext*, const google::protobuf::StringValue&, google::protobuf::Empty*);
//****************************************************************************************************************************************************
/// \brief gRPC client class. This class encapsulate the gRPC service, abstracting all data type conversions.
//****************************************************************************************************************************************************
class GRPCClient: public QObject
{
Q_OBJECT
public: // member functions.
GRPCClient() = default; ///< Default constructor.
GRPCClient(GRPCClient const&) = delete; ///< Disabled copy-constructor.
GRPCClient(GRPCClient&&) = delete; ///< Disabled assignment copy-constructor.
~GRPCClient() override = default; ///< Destructor.
GRPCClient& operator=(GRPCClient const&) = delete; ///< Disabled assignment operator.
GRPCClient& operator=(GRPCClient&&) = delete; ///< Disabled move assignment operator.
bool connectToServer(QString &outError); ///< Establish connection to the gRPC server.
grpc::Status guiReady(); ///< performs the "GuiReady" gRPC call.
grpc::Status isFirstGUIStart(bool &outIsFirst); ///< performs the "IsFirstGUIStart" gRPC call.
grpc::Status isAutostartOn(bool &outIsOn); ///< Performs the "isAutostartOn" gRPC call.
grpc::Status setIsAutostartOn(bool on); ///< Performs the "setIsAutostartOn" gRPC call.
grpc::Status isBetaEnabled(bool &outEnabled); ///< Performs the "isBetaEnabled" gRPC call.
grpc::Status setisBetaEnabled(bool enabled); ///< Performs the 'setIsBetaEnabled' gRPC call.
grpc::Status colorSchemeName(QString &outName); ///< Performs the "colorSchemeName' gRPC call.
grpc::Status setColorSchemeName(QString const &name); ///< Performs the "setColorSchemeName' gRPC call.
grpc::Status currentEmailClient(QString &outName); ///< Performs the 'currentEmailClient' gRPC call.
grpc::Status quit(); ///< Perform the "Quit" gRPC call.
grpc::Status restart(); ///< Performs the Restart gRPC call.
grpc::Status isPortFree(qint32 port, bool &outFree); ///< Performs the 'IsPortFree' call.
grpc::Status showOnStartup(bool &outValue); ///< Performs the 'ShowOnStartup' call.
grpc::Status showSplashScreen(bool &outValue); ///< Performs the 'ShowSplashScreen' call.
grpc::Status goos(QString &outGoos); ///< Performs the 'GoOs' call.
grpc::Status logsPath(QUrl &outPath); ///< Performs the 'LogsPath' call.
grpc::Status licensePath(QUrl &outPath); ///< Performs the 'LicensePath' call.
grpc::Status dependencyLicensesLink(QUrl &outUrl); ///< Performs the 'DependencyLicensesLink' call.
grpc::Status version(QString &outVersion); ///< Performs the 'Version' call.
grpc::Status hostname(QString &outHostname); ///< Performs the 'Hostname' call.
signals: // app related signals
void internetStatus(bool isOn);
void toggleAutostartFinished();
void resetFinished();
void reportBugFinished();
void reportBugSuccess();
void reportBugError();
void showMainWindow();
// cache related calls
public:
grpc::Status isCacheOnDiskEnabled(bool &outEnabled); ///< Performs the 'isCacheOnDiskEnabled' call.
grpc::Status diskCachePath(QUrl &outPath); ///< Performs the 'diskCachePath' call.
grpc::Status changeLocalCache(bool enabled, QUrl const &path); ///< Performs the 'ChangeLocalCache' call.
signals:
void isCacheOnDiskEnabledChanged(bool enabled);
void diskCachePathChanged(QUrl const&outPath);
void cacheUnavailable(); // _ func() `signal:"cacheUnavailable"`
void cacheCantMove(); // _ func() `signal:"cacheCantMove"`
void cacheLocationChangeSuccess(); // _ func() `signal:"cacheLocationChangeSuccess"`
void diskFull(); // _ func() `signal:"diskFull"`
void changeLocalCacheFinished(); // _ func() `signal:"changeLocalCacheFinished"`
// mail settings related calls
public:
grpc::Status useSSLForSMTP(bool &outUseSSL); ///< Performs the 'useSSLForSMTP' gRPC call
grpc::Status setUseSSLForSMTP(bool useSSL); ///< Performs the 'currentEmailClient' gRPC call.
grpc::Status portIMAP(int &outPort); ///< Performs the 'portImap' gRPC call.
grpc::Status portSMTP(int &outPort); ///< Performs the 'portImap' gRPC call.
grpc::Status changePorts(int portIMAP, int portSMTP); ///< Performs the 'changePorts' gRPC call.
grpc::Status isDoHEnabled(bool &outEnabled); ///< Performs the 'isDoHEnabled' gRPC call.
grpc::Status setIsDoHEnabled(bool enabled); ///< Performs the 'setIsDoHEnabled' gRPC call.
signals:
void portIssueIMAP();
void portIssueSMTP();
void toggleUseSSLFinished();
void changePortFinished();
public: // login related calls
grpc::Status login(QString const &username, QString const& password); ///< Performs the 'login' call.
grpc::Status login2FA(QString const &username, QString const& code); ///< Performs the 'login2FA' call.
grpc::Status login2Passwords(QString const &username, QString const& password); ///< Performs the 'login2Passwords' call.
grpc::Status loginAbort(QString const &username); ///< Performs the 'loginAbort' call.
signals:
void loginUsernamePasswordError(QString const &errMsg); // _ func(errorMsg string) `signal:"loginUsernamePasswordError"`
void loginFreeUserError(); // _ func() `signal:"loginFreeUserError"`
void loginConnectionError(QString const &errMsg); // _ func(errorMsg string) `signal:"loginConnectionError"`
void login2FARequested(QString const &userName); // _ func(username string) `signal:"login2FARequested"`
void login2FAError(QString const& errMsg); // _ func(errorMsg string) `signal:"login2FAError"`
void login2FAErrorAbort(QString const& errMsg); // _ func(errorMsg string) `signal:"login2FAErrorAbort"`
void login2PasswordRequested(); // _ func() `signal:"login2PasswordRequested"`
void login2PasswordError(QString const& errMsg); // _ func(errorMsg string) `signal:"login2PasswordError"`
void login2PasswordErrorAbort(QString const& errMsg); // _ func(errorMsg string) `signal:"login2PasswordErrorAbort"`
void loginFinished(QString const &userID); // _ func(index int) `signal:"loginFinished"`
void loginAlreadyLoggedIn(QString const &userID); // _ func(index int) `signal:"loginAlreadyLoggedIn"`
public: // Update related calls
grpc::Status checkUpdate();
grpc::Status installUpdate();
grpc::Status setIsAutomaticUpdateOn(bool on);
grpc::Status isAutomaticUpdateOn(bool &isOn);
signals:
void updateManualError();
void updateForceError();
void updateSilentError();
void updateManualReady(QString const &version);
void updateManualRestartNeeded();
void updateForce(QString const &version);
void updateSilentRestartNeeded();
void updateIsLatestVersion();
void checkUpdatesFinished();
public: // user related calls
grpc::Status getUserList(QList<SPUser>& outUsers);
grpc::Status getUser(QString const &userID, SPUser& outUser);
grpc::Status logoutUser(QString const &userID); ///< Performs the 'logoutUser' call.
grpc::Status removeUser(QString const &userID); ///< Performs the 'removeUser' call.
grpc::Status configureAppleMail(QString const& userID, QString const &address); ///< Performs the 'configureAppleMail' call.
grpc::Status setUserSplitMode(QString const& userID, bool active); ///< Performs the 'SetUserSplitMode' call.
signals:
void toggleSplitModeFinished(QString const& userID);
void userDisconnected(QString const& username);
void userChanged(QString const& userID);
public: // keychain related calls
grpc::Status availableKeychains(QStringList &outKeychains);
grpc::Status currentKeychain(QString &outKeychain);
grpc::Status setCurrentKeychain(QString const &keychain);
signals:
void changeKeychainFinished();
void hasNoKeychain();
void rebuildKeychain();
signals: // mail releated events
void noActiveKeyForRecipient(QString const &email); // _ func(email string) `signal:noActiveKeyForRecipient`
void addressChanged(QString const &address); // _ func(address string) `signal:addressChanged`
void addressChangedLogout(QString const &address); // _ func(address string) `signal:addressChangedLogout`
void apiCertIssue();
public:
grpc::Status startEventStream(); ///< Retrieve and signal the events in the event stream.
grpc::Status stopEventStream(); ///< Stop the event stream.
private:
grpc::Status simpleMethod(SimpleMethod method); ///< perform a gRPC call to a bool setter.
grpc::Status setBool(BoolSetter setter, bool value); ///< perform a gRPC call to a bool setter.
grpc::Status getBool(BoolGetter getter, bool& outValue); ///< perform a gRPC call to a bool getter.
grpc::Status setInt32(Int32Setter setter, int value); ///< perform a gRPC call to an int setter.
grpc::Status getInt32(Int32Getter getter, int& outValue); ///< perform a gRPC call to an int getter.
grpc::Status setString(StringSetter getter, QString const& value); ///< Perform a gRPC call to a string setter.
grpc::Status getString(StringGetter getter, QString& outValue); ///< Perform a gRPC call to a string getter.
grpc::Status getURLForLocalFile(StringGetter getter, QUrl& outValue); ///< Perform a gRPC call to a string getter, with resulted converted to QUrl for a local file path.
grpc::Status getURL(StringGetter getter, QUrl& outValue); ///< Perform a gRPC call to a string getter, with resulted converted to QUrl.
grpc::Status methodWithStringParam(StringParamMethod method, QString const& str); ///< Perfom a gRPC call that takes a string as a parameter and returns an Empty.
void processAppEvent(grpc::AppEvent const &event); ///< Process an 'App' event.
void processLoginEvent(grpc::LoginEvent const &event); ///< Process a 'Login' event.
void processUpdateEvent(grpc::UpdateEvent const &event); ///< Process an 'Update' event.
void processCacheEvent(grpc::CacheEvent const &event); ///< Process a 'Cache' event.
void processMailSettingsEvent(grpc::MailSettingsEvent const &event); ///< Process a 'MailSettings' event.
void processKeychainEvent(grpc::KeychainEvent const &event); ///< Process a 'Keychain' event.
void processMailEvent(grpc::MailEvent const &event); ///< Process a 'Mail' event.
void processUserEvent(grpc::UserEvent const &event); ///< Process a 'User' event.
private: // data members.
std::shared_ptr<grpc::Channel> channel_ { nullptr }; ///< The gRPC channel.
std::shared_ptr<grpc::Bridge::Stub> stub_ { nullptr }; ///< The gRPC stub (a.k.a. client).
};
#endif // BRIDGE_QT6_RPC_CLIENT_H

View File

@ -0,0 +1,63 @@
// 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/>.
#include "GRPCUtils.h"
#include "QMLBackend.h"
//****************************************************************************************************************************************************
/// \param[in] status The status
/// \param[in] callName The call name.
//****************************************************************************************************************************************************
void logGRPCCallStatus(grpc::Status const& status, QString const &callName)
{
if (status.ok())
app().log().debug(QString("%1()").arg(callName));
else
app().log().error(QString("%1() FAILED").arg(callName));
}
//****************************************************************************************************************************************************
/// \param[in] grpcUser the gRPC user struct
/// \return a user.
//****************************************************************************************************************************************************
SPUser parsegrpcUser(grpc::User const &grpcUser)
{
// As we want to use shared pointers here, we do not want to use the Qt ownership system, so we set parent to nil.
// But: From https://doc.qt.io/qt-5/qtqml-cppintegration-data.html:
// " When data is transferred from C++ to QML, the ownership of the data always remains with C++. The exception to this rule
// is when a QObject is returned from an explicit C++ method call: in this case, the QML engine assumes ownership of the object. "
// This is the case here, so we explicitely indicate that the object is owned by C++.
SPUser user = std::make_shared<User>(nullptr);
QQmlEngine::setObjectOwnership(user.get(), QQmlEngine::CppOwnership);
user->setProperty("username", QString::fromStdString(grpcUser.username()));
user->setProperty("avatarText", QString::fromStdString(grpcUser.avatartext()));
user->setProperty("loggedIn", grpcUser.loggedin());
user->setProperty("splitMode", grpcUser.splitmode());
user->setProperty("setupGuideSeen", grpcUser.setupguideseen());
user->setProperty("usedBytes", float(grpcUser.usedbytes()));
user->setProperty("totalBytes", float(grpcUser.totalbytes()));
user->setProperty("password", QString::fromStdString(grpcUser.password()));
QStringList addresses;
for (int j = 0; j < grpcUser.addresses_size(); ++j)
addresses.append(QString::fromStdString(grpcUser.addresses(j)));
user->setProperty("addresses", addresses);
user->setProperty("id", QString::fromStdString(grpcUser.id()));
return user;
}

View File

@ -15,20 +15,16 @@
// You should have received a copy of the GNU General Public License // 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/>. // along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
//go:build darwin && build_qt
// +build darwin,build_qt
package dockicon #ifndef BRIDGE_QT6_GRPCUTILS_H
#define BRIDGE_QT6_GRPCUTILS_H
// #cgo CFLAGS: -x objective-c #include "GRPC/bridge.grpc.pb.h"
// #cgo LDFLAGS: -framework Cocoa #include "grpc++/grpc++.h"
// #include "DockIcon.h" #include "User/User.h"
import "C"
func SetDockIconVisibleState(visible bool) {
C.SetDockIconVisibleState(C.bool(visible))
}
func GetDockIconVisibleState() bool { void logGRPCCallStatus(grpc::Status const& status, QString const &callName); ///< Log the status of a gRPC code.
return bool(C.GetDockIconVisibleState()) SPUser parsegrpcUser(grpc::User const& grpcUser); ///< Parse a gRPC user struct and return a User.
}
#endif // BRIDGE_QT6_GRPCUTILS_H

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,159 @@
// 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/>.
#include "Pch.h"
#include "Log.h"
//****************************************************************************************************************************************************
//
//****************************************************************************************************************************************************
Log::Log()
: QObject()
, stdout_(stdout)
, stderr_(stderr)
{
}
//****************************************************************************************************************************************************
/// \param[in] level The log level.
//****************************************************************************************************************************************************
void Log::setLevel(Log::Level level)
{
QMutexLocker locker(&mutex_);
level_ = level;
}
//****************************************************************************************************************************************************
/// \return The log level.
//****************************************************************************************************************************************************
Log::Level Log::level() const
{
QMutexLocker locker(&mutex_);
return Log::Level::Debug;
}
//****************************************************************************************************************************************************
/// \param[in] value Should the log entries be sent to STDOUT/STDERR.
//****************************************************************************************************************************************************
void Log::setEchoInConsole(bool value)
{
QMutexLocker locker(&mutex_);
echoInConsole_ = value;
}
//****************************************************************************************************************************************************
/// \return true iff the log entries be should sent to STDOUT/STDERR.
//****************************************************************************************************************************************************
bool Log::echoInConsole() const
{
QMutexLocker locker(&mutex_);
return echoInConsole_;
}
//****************************************************************************************************************************************************
/// \param[in] message The message
//****************************************************************************************************************************************************
void Log::debug(QString const &message)
{
QMutexLocker locker(&mutex_);
if (qint32(level_) > qint32(Level::Debug))
return;
emit debugEntryAdded(message);
QString const withPrefix = "[DEBUG] " + message;
emit entryAdded(withPrefix);
if (echoInConsole_)
{
stdout_ << withPrefix << "\n";
stdout_.flush();
}
}
//****************************************************************************************************************************************************
/// \param[in] message The message
//****************************************************************************************************************************************************
void Log::info(QString const &message)
{
QMutexLocker locker(&mutex_);
if (qint32(level_) > qint32(Level::Info))
return;
emit infoEntryAdded(message);
QString const withPrefix = "[INFO] " + message;
emit entryAdded(withPrefix);
if (echoInConsole_)
{
stdout_ << withPrefix << "\n";
stdout_.flush();
}
}
//****************************************************************************************************************************************************
//
//****************************************************************************************************************************************************
void Log::warn(QString const &message)
{
QMutexLocker locker(&mutex_);
if (qint32(level_) > qint32(Level::Warn))
return;
emit infoEntryAdded(message);
QString const withPrefix = "[WARNING] " + message;
emit entryAdded(withPrefix);
if (echoInConsole_)
{
stdout_ << withPrefix << "\n";
stdout_.flush();
}
}
//****************************************************************************************************************************************************
/// message
//****************************************************************************************************************************************************
void Log::error(QString const &message)
{
QMutexLocker locker(&mutex_);
if (qint32(level_) > qint32(Level::Error))
return;
emit infoEntryAdded(message);
QString const withPrefix = "[ERROR] " + message;
emit entryAdded(withPrefix);
if (echoInConsole_)
{
stderr_ << withPrefix << "\n";
stderr_.flush();
}
}

View File

@ -0,0 +1,74 @@
// 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/>.
#ifndef BRIDGE_QT6_LOG_H
#define BRIDGE_QT6_LOG_H
//****************************************************************************************************************************************************
/// \brief Basic log class. No logging to file. Four levels. Rebroadcast received log entries via Qt signals.
//****************************************************************************************************************************************************
class Log : public QObject
{
Q_OBJECT
public: // data types.
enum class Level
{
Debug = 0, ///< Debug
Info = 1, ///< Info
Warn = 2, ///< Warn
Error = 3, ///< Error
}; ///< Log level class
public: // member functions.
Log(); ///< Default constructor.
Log(Log const &) = delete; ///< Disabled copy-constructor.
Log(Log &&) = delete; ///< Disabled assignment copy-constructor.
~Log() override = default; ///< Destructor.
Log &operator=(Log const &) = delete; ///< Disabled assignment operator.
Log &operator=(Log &&) = delete; ///< Disabled move assignment operator.
void setLevel(Level level); ///< Set the log level.
Level level() const; ///< Get the log level.
void setEchoInConsole(bool value); ///< Set if the log entries should be echoed in STDOUT/STDERR.
bool echoInConsole() const; ///< Check if the log entries should be echoed in STDOUT/STDERR.
public slots:
void debug(QString const &message); ///< Adds a debug entry in the log.
void info(QString const &message); ///< Adds an info entry to the log.
void warn(QString const &message); ///< Adds a warning entry to the log.
void error(QString const &message); ///< Adds an error entry to the log.
signals:
void debugEntryAdded(QString const &); ///< Signal for debug entries.
void infoEntryAdded(QString const &message); ///< Signal for info entries.
void warnEntryAdded(QString const &message); ///< Signal for warning entries.
void errorEntryAdded(QString const &message); ///< Signal for error entries.
void entryAdded(QString const &message); ///< Signal emitted when any type of entry is added.
private: // data members
mutable QMutex mutex_; ///< The mutex.
Level level_{Level::Debug}; ///< The log level
bool echoInConsole_{false}; ///< Set if the log messages should be sent to STDOUT/STDERR.
QTextStream stdout_; ///< The stdout stream.
QTextStream stderr_; ///< The stderr stream.
};
#endif //BRIDGE_QT6_LOG_H

View File

@ -15,10 +15,9 @@
// You should have received a copy of the GNU General Public License // 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/>. // along with Proton Mail Bridge. If not, see <https://www.gnu.org/licenses/>.
// +build darwin
// +build build_qt
#include <stdbool.h> #include <QtCore>
#include <QtQuick>
void SetDockIconVisibleState(bool visible); #include <QtQml>
bool GetDockIconVisibleState(); #include <QtQuickControls2>
#include <AppController.h>

View File

@ -0,0 +1,316 @@
// 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/>.
#include "Pch.h"
#include "QMLBackend.h"
#include "Exception.h"
#include "GRPC/GRPCClient.h"
#include "Worker/Overseer.h"
#include "EventStreamWorker.h"
//****************************************************************************************************************************************************
//
//****************************************************************************************************************************************************
QMLBackend::QMLBackend()
: QObject()
, users_(new UserList(this))
{
}
//****************************************************************************************************************************************************
//
//****************************************************************************************************************************************************
void QMLBackend::init()
{
this->connectGrpcEvents();
QString error;
if (app().grpc().connectToServer(error))
app().log().info("Connected to backend via gRPC service.");
else
throw Exception(QString("Cannot connectToServer to go backend via gRPC: %1").arg(error));
eventStreamOverseer_ = std::make_unique<Overseer>(new EventStreamReader(nullptr), nullptr);
eventStreamOverseer_->startWorker(true);
this->retrieveUserList();
}
//****************************************************************************************************************************************************
//
//****************************************************************************************************************************************************
void QMLBackend::connectGrpcEvents()
{
GRPCClient *client = &app().grpc();
// app events
connect(client, &GRPCClient::internetStatus, this, [&](bool isOn) { if (isOn) emit internetOn(); else emit internetOff(); });
connect(client, &GRPCClient::toggleAutostartFinished, this, &QMLBackend::toggleAutostartFinished);
connect(client, &GRPCClient::resetFinished, this, &QMLBackend::resetFinished);
connect(client, &GRPCClient::reportBugFinished, this, &QMLBackend::reportBugFinished);
connect(client, &GRPCClient::reportBugSuccess, this, &QMLBackend::bugReportSendSuccess);
connect(client, &GRPCClient::reportBugError, this, &QMLBackend::bugReportSendError);
// cache events
connect(client, &GRPCClient::isCacheOnDiskEnabledChanged, this, &QMLBackend::isDiskCacheEnabledChanged);
connect(client, &GRPCClient::diskCachePathChanged, this, &QMLBackend::diskCachePathChanged);
connect(client, &GRPCClient::cacheUnavailable, this, &QMLBackend::cacheUnavailable); // _ func() `signal:"cacheUnavailable"`
connect(client, &GRPCClient::cacheCantMove, this, &QMLBackend::cacheCantMove);
connect(client, &GRPCClient::diskFull, this, &QMLBackend::diskFull);
connect(client, &GRPCClient::cacheLocationChangeSuccess, this, &QMLBackend::cacheLocationChangeSuccess);
connect(client, &GRPCClient::changeLocalCacheFinished, this, &QMLBackend::changeLocalCacheFinished);
// login events
connect(client, &GRPCClient::loginUsernamePasswordError, this, &QMLBackend::loginUsernamePasswordError);
connect(client, &GRPCClient::loginFreeUserError, this, &QMLBackend::loginFreeUserError);
connect(client, &GRPCClient::loginConnectionError, this, &QMLBackend::loginConnectionError);
connect(client, &GRPCClient::login2FARequested, this, &QMLBackend::login2FARequested);
connect(client, &GRPCClient::login2FAError, this, &QMLBackend::login2FAError);
connect(client, &GRPCClient::login2FAErrorAbort, this, &QMLBackend::login2FAErrorAbort);
connect(client, &GRPCClient::login2PasswordRequested, this, &QMLBackend::login2PasswordRequested);
connect(client, &GRPCClient::login2PasswordError, this, &QMLBackend::login2PasswordError);
connect(client, &GRPCClient::login2PasswordErrorAbort, this, &QMLBackend::login2PasswordErrorAbort);
connect(client, &GRPCClient::loginFinished, this, [&](QString const &userID) {
qint32 const index = users_->rowOfUserID(userID); emit loginFinished(index); });
connect(client, &GRPCClient::loginAlreadyLoggedIn, this, [&](QString const &userID) {
qint32 const index = users_->rowOfUserID(userID); emit loginAlreadyLoggedIn(index); });
// mail settings events
connect(client, &GRPCClient::portIssueIMAP, this, &QMLBackend::portIssueIMAP);
connect(client, &GRPCClient::portIssueSMTP, this, &QMLBackend::portIssueSMTP);
connect(client, &GRPCClient::toggleUseSSLFinished, this, &QMLBackend::toggleUseSSLFinished);
connect(client, &GRPCClient::changePortFinished, this, &QMLBackend::changePortFinished);
// keychain events
connect(client, &GRPCClient::changeKeychainFinished, this, &QMLBackend::changeKeychainFinished);
connect(client, &GRPCClient::hasNoKeychain, this, &QMLBackend::notifyHasNoKeychain);
connect(client, &GRPCClient::rebuildKeychain, this, &QMLBackend::notifyRebuildKeychain);
// mail events
connect(client, &GRPCClient::noActiveKeyForRecipient, this, &QMLBackend::noActiveKeyForRecipient);
connect(client, &GRPCClient::addressChanged, this, &QMLBackend::addressChanged);
connect(client, &GRPCClient::addressChangedLogout, this, &QMLBackend::addressChangedLogout);
connect(client, &GRPCClient::apiCertIssue, this, &QMLBackend::apiCertIssue);
// user events
connect(client, &GRPCClient::userDisconnected, this, &QMLBackend::userDisconnected);
users_->connectGRPCEvents();
}
//****************************************************************************************************************************************************
//
//****************************************************************************************************************************************************
void QMLBackend::retrieveUserList()
{
QList<SPUser> users;
logGRPCCallStatus(app().grpc().getUserList(users), "getUserList");
users_->reset(users);
}
//****************************************************************************************************************************************************
//
//****************************************************************************************************************************************************
void QMLBackend::clearUserList()
{
users_->reset();
}
//****************************************************************************************************************************************************
//
//****************************************************************************************************************************************************
QPoint QMLBackend::getCursorPos()
{
return QCursor::pos();
}
//****************************************************************************************************************************************************
//
//****************************************************************************************************************************************************
bool QMLBackend::isPortFree(int port)
{
bool isFree = false;
logGRPCCallStatus(app().grpc().isPortFree(port, isFree), "isPortFree");
return isFree;
}
//****************************************************************************************************************************************************
//
//****************************************************************************************************************************************************
void QMLBackend::guiReady()
{
logGRPCCallStatus(app().grpc().guiReady(), "guiReady");
}
//****************************************************************************************************************************************************
//
//****************************************************************************************************************************************************
void QMLBackend::quit()
{
logGRPCCallStatus(app().grpc().quit(), "quit");
qApp->exit(0);
}
//****************************************************************************************************************************************************
//
//****************************************************************************************************************************************************
void QMLBackend::restart()
{
logGRPCCallStatus(app().grpc().restart(), "restart");
app().log().error("RESTART is not implemented"); /// \todo GODT-1671 implement restart.
}
//****************************************************************************************************************************************************
/// \param[in] active Should we activate autostart.
//****************************************************************************************************************************************************
void QMLBackend::toggleAutostart(bool active)
{
logGRPCCallStatus(app().grpc().setIsAutostartOn(active), "setIsAutostartOn");
emit isAutostartOnChanged(this->isAutostartOn());
}
//****************************************************************************************************************************************************
/// \param[in] active The new state for the beta enabled property.
//****************************************************************************************************************************************************
void QMLBackend::toggleBeta(bool active)
{
logGRPCCallStatus(app().grpc().setisBetaEnabled(active), "setIsBetaEnabled");
emit isBetaEnabledChanged(this->isBetaEnabled());
}
//****************************************************************************************************************************************************
/// \param[in] scheme the scheme name
//****************************************************************************************************************************************************
void QMLBackend::changeColorScheme(QString const &scheme)
{
logGRPCCallStatus(app().grpc().setColorSchemeName(scheme), "setIsBetaEnabled");
emit colorSchemeNameChanged(this->colorSchemeName());
}
//****************************************************************************************************************************************************
/// \param[in] makeItActive Should SSL for SMTP be enabled.
//****************************************************************************************************************************************************
void QMLBackend::toggleUseSSLforSMTP(bool makeItActive)
{
grpc::Status status = app().grpc().setUseSSLForSMTP(makeItActive);
logGRPCCallStatus(status, "setUseSSLForSMTP");
if (status.ok())
emit useSSLforSMTPChanged(makeItActive);
}
//****************************************************************************************************************************************************
/// \param[in] imapPort The IMAP port.
/// \param[in] smtpPort The SMTP port.
//****************************************************************************************************************************************************
void QMLBackend::changePorts(int imapPort, int smtpPort)
{
grpc::Status status = app().grpc().changePorts(imapPort, smtpPort);
logGRPCCallStatus(status, "changePorts");
if (status.ok())
{
emit portIMAPChanged(imapPort);
emit portSMTPChanged(smtpPort);
}
}
//****************************************************************************************************************************************************
/// \param[in] active Should DoH be active.
//****************************************************************************************************************************************************
void QMLBackend::toggleDoH(bool active)
{
grpc::Status status = app().grpc().setIsDoHEnabled(active);
logGRPCCallStatus(status, "toggleDoH");
if (status.ok())
emit isDoHEnabledChanged(active);
}
//****************************************************************************************************************************************************
/// \param[in] keychain The new keychain.
//****************************************************************************************************************************************************
void QMLBackend::changeKeychain(QString const &keychain)
{
grpc::Status status = app().grpc().setCurrentKeychain(keychain);
logGRPCCallStatus(status, "setCurrentKeychain");
if (status.ok())
emit currentKeychainChanged(keychain);
}
//****************************************************************************************************************************************************
/// \param[in] active Should automatic update be turned on.
//****************************************************************************************************************************************************
void QMLBackend::toggleAutomaticUpdate(bool active)
{
grpc::Status status = app().grpc().setIsAutomaticUpdateOn(active);
logGRPCCallStatus(status, "toggleAutomaticUpdate");
if (status.ok())
emit isAutomaticUpdateOnChanged(active);
}
//****************************************************************************************************************************************************
//
//****************************************************************************************************************************************************
void QMLBackend::checkUpdates()
{
app().log().error(QString("%1() is not implemented.").arg(__FUNCTION__));
}
//****************************************************************************************************************************************************
//
//****************************************************************************************************************************************************
void QMLBackend::installUpdate()
{
app().log().error(QString("%1() is not implemented.").arg(__FUNCTION__));
}
//****************************************************************************************************************************************************
//
//****************************************************************************************************************************************************
void QMLBackend::triggerReset()
{
app().log().error(QString("%1() is not implemented.").arg(__FUNCTION__));
}
//****************************************************************************************************************************************************
//
//****************************************************************************************************************************************************
void QMLBackend::reportBug(QString const &description, QString const &address, QString const &emailClient,
bool includeLogs)
{
Q_UNUSED(includeLogs)
app().log().error(QString("%1() is not implemented.").arg(__FUNCTION__));
}

View File

@ -0,0 +1,222 @@
// 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/>.
#ifndef BRIDGE_QT6_QMLBACKEND_H
#define BRIDGE_QT6_QMLBACKEND_H
#include <grpcpp/support/status.h>
#include "DockIcon/DockIcon.h"
#include "GRPC/GRPCClient.h"
#include "GRPC/GRPCUtils.h"
#include "Worker/Overseer.h"
#include "User/UserList.h"
//****************************************************************************************************************************************************
/// \brief Bridge C++ backend class.
//****************************************************************************************************************************************************
class QMLBackend: public QObject
{
Q_OBJECT
public: // member functions.
QMLBackend(); ///< Default constructor.
QMLBackend(QMLBackend const &) = delete; ///< Disabled copy-constructor.
QMLBackend(QMLBackend &&) = delete; ///< Disabled assignment copy-constructor.
~QMLBackend() override = default; ///< Destructor.
QMLBackend &operator=(QMLBackend const &) = delete; ///< Disabled assignment operator.
QMLBackend &operator=(QMLBackend &&) = delete; ///< Disabled move assignment operator.
void init(); ///< Initialize the backend.
void clearUserList(); ///< Clear the user list.
// invokable methods can be called from QML. They generally return a value, which slots cannot do.
Q_INVOKABLE static QPoint getCursorPos(); // _ func() *core.QPoint `slot:"getCursorPos"`
Q_INVOKABLE static bool isPortFree(int port); // _ func(port int) bool `slot:"isPortFree"`
public: // Qt/QML properties. Note that the NOTIFY-er signal is required even for read-only properties (QML warning otherwise)
Q_PROPERTY(bool showOnStartup READ showOnStartup NOTIFY showOnStartupChanged) // _ bool `property:showOnStartup`
Q_PROPERTY(bool showSplashScreen READ showSplashScreen NOTIFY showSplashScreenChanged) // _ bool `property:showSplashScreen`
Q_PROPERTY(QString goos READ goos NOTIFY goosChanged) // _ string `property:"goos"`
Q_PROPERTY(QUrl logsPath READ logsPath NOTIFY logsPathChanged) // _ core.QUrl `property:"logsPath"`
Q_PROPERTY(QUrl licensePath READ licensePath NOTIFY licensePathChanged) // _ core.QUrl `property:"licensePath"`
Q_PROPERTY(QUrl releaseNotesLink READ releaseNotesLink WRITE setReleaseNotesLink NOTIFY releaseNotesLinkChanged) // _ core.QUrl `property:"releaseNotesLink"`
Q_PROPERTY(QUrl dependencyLicensesLink READ dependencyLicensesLink NOTIFY dependencyLicensesLinkChanged) // _ core.QUrl `property:"dependencyLicensesLink"`
Q_PROPERTY(QUrl landingPageLink READ landingPageLink WRITE setLandingPageLink NOTIFY landingPageLinkChanged) // _ core.QUrl `property:"landingPageLink"`
Q_PROPERTY(QString version READ version NOTIFY versionChanged) // _ string `property:"version"`
Q_PROPERTY(QString hostname READ hostname NOTIFY hostnameChanged) // _ string `property:"hostname"`
Q_PROPERTY(bool isAutostartOn READ isAutostartOn NOTIFY isAutostartOnChanged) // _ bool `property:"isAutostartOn"`
Q_PROPERTY(bool isBetaEnabled READ isBetaEnabled NOTIFY isBetaEnabledChanged) // _ bool `property:"isBetaEnabled"`
Q_PROPERTY(QString colorSchemeName READ colorSchemeName NOTIFY colorSchemeNameChanged) // _ string `property:"colorSchemeName"`
Q_PROPERTY(bool isDiskCacheEnabled READ isDiskCacheEnabled NOTIFY isDiskCacheEnabledChanged) // _ bool `property:"isDiskCacheEnabled"`
Q_PROPERTY(QUrl diskCachePath READ diskCachePath NOTIFY diskCachePathChanged) // _ core.QUrl `property:"diskCachePath"`
Q_PROPERTY(bool useSSLforSMTP READ useSSLForSMTP NOTIFY useSSLforSMTPChanged) // _ bool `property:"useSSLforSMTP"`
Q_PROPERTY(int portIMAP READ portIMAP NOTIFY portIMAPChanged) // _ int `property:"portIMAP"`
Q_PROPERTY(int portSMTP READ portSMTP NOTIFY portSMTPChanged) // _ int `property:"portSMTP"`
Q_PROPERTY(bool isDoHEnabled READ isDoHEnabled NOTIFY isDoHEnabledChanged) // _ bool `property:"isDoHEnabled"`
Q_PROPERTY(bool isFirstGUIStart READ isFirstGUIStart) // _ bool `property:"isFirstGUIStart"`
Q_PROPERTY(bool isAutomaticUpdateOn READ isAutomaticUpdateOn NOTIFY isAutomaticUpdateOnChanged) // _ bool `property:"isAutomaticUpdateOn"`
Q_PROPERTY(QString currentEmailClient READ currentEmailClient NOTIFY currentEmailClientChanged) // _ string `property:"currentEmailClient"`
Q_PROPERTY(QStringList availableKeychain READ availableKeychain NOTIFY availableKeychainChanged) // _ []string `property:"availableKeychain"`
Q_PROPERTY(QString currentKeychain READ currentKeychain NOTIFY currentKeychainChanged) // _ string `property:"currentKeychain"`
Q_PROPERTY(UserList* users MEMBER users_ NOTIFY usersChanged)
Q_PROPERTY(bool dockIconVisible READ dockIconVisible WRITE setDockIconVisible NOTIFY dockIconVisibleChanged) // _ bool `property:dockIconVisible`
// Qt Property system setters & getters.
bool showOnStartup() const { bool v = false; logGRPCCallStatus(app().grpc().showOnStartup(v), "showOnStartup"); return v; };
bool showSplashScreen() { bool show = false; logGRPCCallStatus(app().grpc().showSplashScreen(show), "showSplashScreen"); return show; }
QString goos() { QString goos; logGRPCCallStatus(app().grpc().goos(goos), "goos"); return goos; }
QUrl logsPath() const { QUrl path; logGRPCCallStatus(app().grpc().logsPath(path), "logsPath"); return path;}
QUrl licensePath() const { QUrl path; logGRPCCallStatus(app().grpc().licensePath(path), "licensePath"); return path; }
QUrl releaseNotesLink() const { return releaseNotesLink_; }
void setReleaseNotesLink(QUrl const& url) { if (url != releaseNotesLink_) { releaseNotesLink_ = url; emit releaseNotesLinkChanged(url); } }
QUrl dependencyLicensesLink() const { QUrl link; logGRPCCallStatus(app().grpc().dependencyLicensesLink(link), "dependencyLicensesLink"); return link; }
QUrl landingPageLink() const { return landingPageLink_; }
void setLandingPageLink(QUrl const& url) { if (url != landingPageLink_) { landingPageLink_ = url; emit landingPageLinkChanged(url); } }
QString version() const { QString version; logGRPCCallStatus(app().grpc().version(version), "version"); return version; }
QString hostname() const { QString hostname; logGRPCCallStatus(app().grpc().hostname(hostname), "hostname"); return hostname; }
bool isAutostartOn() const { bool v; logGRPCCallStatus(app().grpc().isAutostartOn(v), "isAutostartOn"); return v; };
bool isBetaEnabled() const { bool v; logGRPCCallStatus(app().grpc().isBetaEnabled(v), "isBetaEnabled"); return v; }
QString colorSchemeName() const { QString name; logGRPCCallStatus(app().grpc().colorSchemeName(name), "colorSchemeName"); return name; }
bool isDiskCacheEnabled() const { bool enabled; logGRPCCallStatus(app().grpc().isCacheOnDiskEnabled(enabled), "isCacheOnDiskEnabled"); return enabled;}
QUrl diskCachePath() const { QUrl path; logGRPCCallStatus(app().grpc().diskCachePath(path), "diskCachePath"); return path; }
bool useSSLForSMTP() const{ bool useSSL; logGRPCCallStatus(app().grpc().useSSLForSMTP(useSSL), "useSSLForSMTP"); return useSSL; }
int portIMAP() const { int port; logGRPCCallStatus(app().grpc().portIMAP(port), "portIMAP"); return port; }
int portSMTP() const { int port; logGRPCCallStatus(app().grpc().portSMTP(port), "portSMTP"); return port; }
bool isDoHEnabled() const { bool isEnabled; logGRPCCallStatus(app().grpc().isDoHEnabled(isEnabled), "isDoHEnabled"); return isEnabled;}
bool isFirstGUIStart() const { bool v; logGRPCCallStatus(app().grpc().isFirstGUIStart(v), "isFirstGUIStart"); return v; };
bool isAutomaticUpdateOn() const { bool isOn = false; logGRPCCallStatus(app().grpc().isAutomaticUpdateOn(isOn), "isAutomaticUpdateOn"); return isOn; }
QString currentEmailClient() { QString client; logGRPCCallStatus(app().grpc().currentEmailClient(client), "currentEmailClient"); return client;}
QStringList availableKeychain() const { QStringList keychains; logGRPCCallStatus(app().grpc().availableKeychains(keychains), "availableKeychain"); return keychains; }
QString currentKeychain() const { QString keychain; logGRPCCallStatus(app().grpc().currentKeychain(keychain), "currentKeychain"); return keychain; }
bool dockIconVisible() const { return getDockIconVisibleState(); };
void setDockIconVisible(bool visible) { setDockIconVisibleState(visible); emit dockIconVisibleChanged(visible); }
signals: // Signal used by the Qt property system. Many of them are unused but required to avoir warning from the QML engine.
void showSplashScreenChanged(bool value);
void showOnStartupChanged(bool value);
void goosChanged(QString const &value);
void isDiskCacheEnabledChanged(bool value);
void diskCachePathChanged(QUrl const &url);
void useSSLforSMTPChanged(bool value);
void isAutomaticUpdateOnChanged(bool value);
void isBetaEnabledChanged(bool value);
void colorSchemeNameChanged(QString const &scheme);
void isDoHEnabledChanged(bool value);
void logsPathChanged(QUrl const &path);
void licensePathChanged(QUrl const &path);
void releaseNotesLinkChanged(QUrl const &link);
void dependencyLicensesLinkChanged(QUrl const &link);
void landingPageLinkChanged(QUrl const &link);
void versionChanged(QString const &version);
void currentEmailClientChanged(QString const &email);
void currentKeychainChanged(QString const &keychain);
void availableKeychainChanged(QStringList const &keychains);
void hostnameChanged(QString const &hostname);
void isAutostartOnChanged(bool value);
void portIMAPChanged(int port);
void portSMTPChanged(int port);
void usersChanged(UserList* users);
void dockIconVisibleChanged(bool value);
public slots: // slot for signals received from QML -> To be forwarded to Bridge via RPC Client calls.
void toggleAutostart(bool active); // _ func(makeItActive bool) `slot:"toggleAutostart"`
void toggleBeta(bool active); // _ func(makeItActive bool) `slot:"toggleBeta"`
void changeColorScheme(QString const &scheme); // _ func(string) `slot:"changeColorScheme"`
void changeLocalCache(bool enable, QUrl const& path) { logGRPCCallStatus(app().grpc().changeLocalCache(enable, path), "changeLocalCache"); } // _ func(enableDiskCache bool, diskCachePath core.QUrl) `slot:"changeLocalCache"`
void login(QString const& username, QString const& password) { logGRPCCallStatus(app().grpc().login(username, password), "login");} // _ func(username, password string) `slot:"login"`
void login2FA(QString const& username, QString const& code) { logGRPCCallStatus(app().grpc().login2FA(username, code), "login2FA");} // _ func(username, code string) `slot:"login2FA"`
void login2Password(QString const& username, QString const& password) { logGRPCCallStatus(app().grpc().login2Passwords(username, password),
"login2Passwords");} // _ func(username, password string) `slot:"login2Password"`
void loginAbort(QString const& username){ logGRPCCallStatus(app().grpc().loginAbort(username), "loginAbort");} // _ func(username string) `slot:"loginAbort"`
void toggleUseSSLforSMTP(bool makeItActive); // _ func(makeItActive bool) `slot:"toggleUseSSLforSMTP"`
void changePorts(int imapPort, int smtpPort); // _ func(imapPort, smtpPort int) `slot:"changePorts"`
void toggleDoH(bool active); // _ func(makeItActive bool) `slot:"toggleDoH"`
void toggleAutomaticUpdate(bool makeItActive); // _ func(makeItActive bool) `slot:"toggleAutomaticUpdate"`
void updateCurrentMailClient() { emit currentEmailClientChanged(currentEmailClient()); } // _ func() `slot:"updateCurrentMailClient"`
void changeKeychain(QString const &keychain); // _ func(keychain string) `slot:"changeKeychain"`
void guiReady(); // _ func() `slot:"guiReady"`
void quit(); // _ func() `slot:"quit"`
void restart(); // _ func() `slot:"restart"`
void checkUpdates(); // _ func() `slot:"checkUpdates"`
void installUpdate(); // _ func() `slot:"installUpdate"`
void triggerReset(); // _ func() `slot:"triggerReset"`
void reportBug(QString const &description, QString const& address, QString const &emailClient, bool includeLogs); // _ func(description, address, emailClient string, includeLogs bool) `slot:"reportBug"`
signals: // Signals received from the Go backend, to be forwarded to QML
void toggleAutostartFinished(); // _ func() `signal:"toggleAutostartFinished"`
void cacheUnavailable(); // _ func() `signal:"cacheUnavailable"`
void cacheCantMove(); // _ func() `signal:"cacheCantMove"`
void cacheLocationChangeSuccess(); // _ func() `signal:"cacheLocationChangeSuccess"`
void diskFull(); // _ func() `signal:"diskFull"`
void changeLocalCacheFinished(); // _ func() `signal:"changeLocalCacheFinished"`
void loginUsernamePasswordError(QString const &errorMsg); // _ func(errorMsg string) `signal:"loginUsernamePasswordError"`
void loginFreeUserError(); // _ func() `signal:"loginFreeUserError"`
void loginConnectionError(QString const &errorMsg); // _ func(errorMsg string) `signal:"loginConnectionError"`
void login2FARequested(QString const &username); // _ func(username string) `signal:"login2FARequested"`
void login2FAError(QString const& errorMsg); // _ func(errorMsg string) `signal:"login2FAError"`
void login2FAErrorAbort(QString const& errorMsg); // _ func(errorMsg string) `signal:"login2FAErrorAbort"`
void login2PasswordRequested(); // _ func() `signal:"login2PasswordRequested"`
void login2PasswordError(QString const& errorMsg); // _ func(errorMsg string) `signal:"login2PasswordError"`
void login2PasswordErrorAbort(QString const& errorMsg); // _ func(errorMsg string) `signal:"login2PasswordErrorAbort"`
void loginFinished(int index); // _ func(index int) `signal:"loginFinished"`
void loginAlreadyLoggedIn(int index); // _ func(index int) `signal:"loginAlreadyLoggedIn"`
void updateManualReady(QString const& version); // _ func(version string) `signal:"updateManualReady"`
void updateManualRestartNeeded(); // _ func() `signal:"updateManualRestartNeeded"`
void updateManualError(); // _ func() `signal:"updateManualError"`
void updateForce(QString const& version); // _ func(version string) `signal:"updateForce"`
void updateForceError(); // _ func() `signal:"updateForceError"`
void updateSilentRestartNeeded(); // _ func() `signal:"updateSilentRestartNeeded"`
void updateSilentError(); // _ func() `signal:"updateSilentError"`
void updateIsLatestVersion(); // _ func() `signal:"updateIsLatestVersion"`
void checkUpdatesFinished(); // _ func() `signal:"checkUpdatesFinished"`
void toggleUseSSLFinished(); // _ func() `signal:"toggleUseSSLFinished"`
void changePortFinished(); // _ func() `signal:"changePortFinished"`
void portIssueIMAP(); // _ func() `signal:"portIssueIMAP"`
void portIssueSMTP(); // _ func() `signal:"portIssueSMTP"`
void changeKeychainFinished(); // _ func() `signal:"changeKeychainFinished"`
void notifyHasNoKeychain(); // _ func() `signal:"notifyHasNoKeychain"`
void notifyRebuildKeychain(); // _ func() `signal:"notifyRebuildKeychain"`
void noActiveKeyForRecipient(QString const& email); // _ func(email string) `signal:noActiveKeyForRecipient`
void addressChanged(QString const& address); // _ func(address string) `signal:addressChanged`
void addressChangedLogout(QString const& address); // _ func(address string) `signal:addressChangedLogout`
void apiCertIssue(); // _ func() `signal:apiCertIssue`
void userDisconnected(QString const& username); // _ func(username string) `signal:userDisconnected`
void internetOff(); // _ func() `signal:"internetOff"`
void internetOn(); // _ func() `signal:"internetOn"`
void resetFinished(); // _ func() `signal:"resetFinished"`
void reportBugFinished(); // _ func() `signal:"reportBugFinished"`
void bugReportSendSuccess(); // _ func() `signal:"bugReportSendSuccess"`
void bugReportSendError(); // _ func() `signal:"bugReportSendError"`
void showMainWindow(); // _ func() `signal:showMainWindow`
private: // member functions
void retrieveUserList(); ///< Retrieve the list of users via gRPC.
void connectGrpcEvents(); ///< Connect gRPC that need to be forwarded to QML via backend signals
private: // data members
UserList* users_ { nullptr }; ///< The user list. Owned by backend.
std::unique_ptr<Overseer> eventStreamOverseer_; ///< The event stream overseer.
QUrl releaseNotesLink_; /// Release notes is not stored in the backend, it's pushed by the update check so we keep a local copy of it. \todo GODT-1670 Check this is implemented.
QUrl landingPageLink_; /// Landing page link is not stored in the backend, it's pushed by the update check so we keep a local copy of it. \todo GODT-1670 Check this is implemented.
friend class AppController;
};
#endif // BRIDGE_QT6_QMLBACKEND_H

View File

@ -0,0 +1,95 @@
// 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/>.
#include "Pch.h"
#include "User.h"
#include "GRPC/GRPCUtils.h"
#include "GRPC/GRPCClient.h"
//****************************************************************************************************************************************************
/// \param[in] parent The parent object.
//****************************************************************************************************************************************************
User::User(QObject *parent)
: QObject(parent)
{
}
//****************************************************************************************************************************************************
/// \param[in] user The user to copy from
//****************************************************************************************************************************************************
void User::update(User const &user)
{
this->setProperty("username", user.username_);
this->setProperty("avatarText", user.avatarText_);
this->setProperty("loggedIn", user.loggedIn_);
this->setProperty("splitMode", user.splitMode_);
this->setProperty("setupGuideSeen", user.setupGuideSeen_);
this->setProperty("usedBytes", user.usedBytes_);
this->setProperty("totalBytes", user.totalBytes_);
this->setProperty("password", user.password_);
this->setProperty("addresses", user.addresses_);
this->setProperty("id", user.id_);
}
//****************************************************************************************************************************************************
/// \param[in] makeItActive Should split mode be made active.
//****************************************************************************************************************************************************
void User::toggleSplitMode(bool makeItActive)
{
logGRPCCallStatus(app().grpc().setUserSplitMode(id_, makeItActive), "toggleSplitMode");
}
//****************************************************************************************************************************************************
//
//****************************************************************************************************************************************************
void User::logout()
{
logGRPCCallStatus(app().grpc().logoutUser(id_), "logoutUser");
}
//****************************************************************************************************************************************************
//
//****************************************************************************************************************************************************
void User::remove()
{
logGRPCCallStatus(app().grpc().removeUser(id_), "removeUser");
}
//****************************************************************************************************************************************************
/// \param[in] address The email address to configure Apple Mail for.
//****************************************************************************************************************************************************
void User::configureAppleMail(QString const &address)
{
logGRPCCallStatus(app().grpc().configureAppleMail(id_, address), "configureAppleMail");
}
//****************************************************************************************************************************************************
// The only purpose of this call is to forward to the QML application the toggleSplitModeFinished(userID) event
// that was received by the UserList model.
//****************************************************************************************************************************************************
void User::emitToggleSplitModeFinished()
{
this->setProperty("splitMode", QVariant::fromValue(!this->property("splitMode").toBool()));
emit toggleSplitModeFinished();
}

View File

@ -0,0 +1,93 @@
// 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/>.
#ifndef BRIDGE_QT6_USER_H
#define BRIDGE_QT6_USER_H
#include "Log.h"
//****************************************************************************************************************************************************
/// \brief User class.
//****************************************************************************************************************************************************
class User : public QObject
{
Q_OBJECT
public: // member functions.
explicit User(QObject *parent = nullptr); ///< Default constructor.
User(User const &) = delete; ///< Disabled copy-constructor.
User(User &&) = delete; ///< Disabled assignment copy-constructor.
~User() override = default; ///< Destructor.
User &operator=(User const &) = delete; ///< Disabled assignment operator.
User &operator=(User &&) = delete; ///< Disabled move assignment operator.
void update(User const &user); ///< Update the user
public slots:
// slots for QML generated calls
void toggleSplitMode(bool makeItActive); // _ func(makeItActive bool) `slot:"toggleSplitMode"`
void logout(); // _ func() `slot:"logout"`
void remove(); // _ func() `slot:"remove"`
void configureAppleMail(QString const &address); // _ func(address string) `slot:"configureAppleMail"`
// slots for external signals
void emitToggleSplitModeFinished();
public:
Q_PROPERTY(QString username MEMBER username_ NOTIFY usernameChanged) // _ string `property:"username"`
Q_PROPERTY(QString avatarText MEMBER avatarText_ NOTIFY avatarTextChanged) // _ string `property:"avatarText"`
Q_PROPERTY(bool loggedIn MEMBER loggedIn_ NOTIFY loggedInChanged) // _ bool `property:"loggedIn"`
Q_PROPERTY(bool splitMode MEMBER splitMode_ NOTIFY splitModeChanged) // _ bool `property:"splitMode"`
Q_PROPERTY(bool setupGuideSeen MEMBER setupGuideSeen_ NOTIFY setupGuideSeenChanged) // _ bool `property:"setupGuideSeen"`
Q_PROPERTY(float usedBytes MEMBER usedBytes_ NOTIFY usedBytesChanged) // _ float32 `property:"usedBytes"`
Q_PROPERTY(float totalBytes MEMBER totalBytes_ NOTIFY totalBytesChanged) // _ float32 `property:"totalBytes"`
Q_PROPERTY(QString password MEMBER password_ NOTIFY passwordChanged) // _ string `property:"password"`
Q_PROPERTY(QStringList addresses MEMBER addresses_ NOTIFY addressesChanged) // _ []string `property:"addresses"`
Q_PROPERTY(QString id MEMBER id_ NOTIFY idChanged) // _ string ID
signals:
// signals used for Qt properties
void usernameChanged(QString const &username);
void avatarTextChanged(QString const &avatarText);
void loggedInChanged(bool loggedIn);
void splitModeChanged(bool splitMode);
void setupGuideSeenChanged(bool seen);
void usedBytesChanged(float byteCount);
void totalBytesChanged(float byteCount);
void passwordChanged(QString const &);
void addressesChanged(QStringList const &);
void idChanged(QStringList const &id);
void toggleSplitModeFinished();
private:
QString id_; ///< The userID.
QString username_; ///< The username
QString avatarText_; ///< The avatar text (i.e. initials of the user)
bool loggedIn_{false}; ///< Is the user logged in.
bool splitMode_{false}; ///< Is split mode active.
bool setupGuideSeen_{false}; ///< Has the setup guide been seen.
float usedBytes_{0.0f}; ///< The storage used by the user.
float totalBytes_{0.0f}; ///< The storage quota of the user.
QString password_; ///< The IMAP password of the user.
QStringList addresses_; ///< The email address list of the user.
};
typedef std::shared_ptr<User> SPUser;
#endif // BRIDGE_QT6_USER_H

View File

@ -0,0 +1,220 @@
// 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/>.
#include "Pch.h"
#include "UserList.h"
#include "GRPC/GRPCClient.h"
//****************************************************************************************************************************************************
/// \param[in] parent The parent object of the user list.
//****************************************************************************************************************************************************
UserList::UserList(QObject *parent)
: QAbstractListModel(parent)
{
/// \todo use mutex to prevent concurrent access
}
//****************************************************************************************************************************************************
//
//****************************************************************************************************************************************************
void UserList::connectGRPCEvents() const
{
GRPCClient* client = &app().grpc();
connect(client, &GRPCClient::userChanged, this, &UserList::onUserChanged);
connect(client, &GRPCClient::toggleSplitModeFinished, this, &UserList::onToggleSplitModeFinished);
}
//****************************************************************************************************************************************************
//
//****************************************************************************************************************************************************
int UserList::rowCount(QModelIndex const &) const
{
return users_.size();
}
//****************************************************************************************************************************************************
/// \param[in] index The index to retrieve data for.
/// \param[in] role The role to retrieve data for.
/// \return The data at the index for the given role.
//****************************************************************************************************************************************************
QVariant UserList::data(QModelIndex const &index, int role) const
{
/// This It does not seem to be used, but the method is required by the base class.
/// From the original QtThe recipe QML backend User model, the User is always returned, regardless of the role.
Q_UNUSED(role)
int const row = index.row();
if ((row < 0) || (row >= users_.size()))
return QVariant();
return QVariant::fromValue(users_[row].get());
}
//****************************************************************************************************************************************************
/// \param[in] userID The userID.
/// \return the row of the user.
/// \return -1 if the userID is not in the list
//****************************************************************************************************************************************************
int UserList::rowOfUserID(QString const &userID) const
{
for (qint32 row = 0; row < users_.count(); ++row)
if (userID == users_[row]->property("id"))
return row;
return -1;
}
//****************************************************************************************************************************************************
//
//****************************************************************************************************************************************************
void UserList::reset()
{
this->beginResetModel();
users_.clear();
this->endResetModel();
}
//****************************************************************************************************************************************************
/// \param[in] users The new user list.
//****************************************************************************************************************************************************
void UserList::reset(QList<SPUser> const &users)
{
this->beginResetModel();
users_ = users;
this->endResetModel();
}
//****************************************************************************************************************************************************
/// \param[in] user The user.
//****************************************************************************************************************************************************
void UserList::appendUser(SPUser const& user)
{
int const size = users_.size();
this->beginInsertRows(QModelIndex(), size, size);
users_.append(user);
this->endInsertRows();
}
//****************************************************************************************************************************************************
/// \param[in] row The row.
//****************************************************************************************************************************************************
void UserList::removeUserAt(int row)
{
if ((row < 0) && (row >= users_.size()))
return;
this->beginRemoveRows(QModelIndex(), row, row);
users_.removeAt(row);
this->endRemoveRows();
}
//****************************************************************************************************************************************************
/// \param[in] row The row.
/// \param[in] user The user.
//****************************************************************************************************************************************************
void UserList::updateUserAtRow(int row, User const &user)
{
if ((row < 0) || (row >= users_.count()))
{
app().log().error(QString("invalid user at row %2 (user count = %2)").arg(row).arg(users_.count()));
return;
}
users_[row]->update(user);
QModelIndex modelIndex = this->index(row);
emit dataChanged(modelIndex, modelIndex);
}
//****************************************************************************************************************************************************
/// \param[in] row The row.
//****************************************************************************************************************************************************
User *UserList::get(int row) const
{
if ((row < 0) || (row >= users_.count()))
{
app().log().error(QString("Requesting invalid user at row %1 (user count = %2)").arg(row).arg(users_.count()));
return nullptr;
}
app().log().debug(QString("Retrieving user at row %1 (user count = %2)").arg(row).arg(users_.count()));
return users_[row].get();
}
//****************************************************************************************************************************************************
/// \param[in] userID The userID.
//****************************************************************************************************************************************************
void UserList::onUserChanged(QString const &userID)
{
int const index = this->rowOfUserID(userID);
SPUser user;
grpc::Status status = app().grpc().getUser(userID, user);
if ((!user) || (!status.ok()))
{
if (index >= 0) // user exists here but not in the go backend. we delete it.
{
app().log().debug(QString("Removing user from userlist: %1").arg(userID));
this->removeUserAt(index);
}
return;
}
if (index < 0)
{
app().log().debug(QString("Adding user in userlist: %1").arg(userID));
this->appendUser(user);
return;
}
app().log().debug(QString("Updating user in userlist: %1").arg(userID));
this->updateUserAtRow(index, *user);
}
//****************************************************************************************************************************************************
/// The only purpose of this function is to forward the toggleSplitModeFinished event received from gRPC to the
/// appropriate user.
///
/// \param[in] userID the userID.
//****************************************************************************************************************************************************
void UserList::onToggleSplitModeFinished(QString const &userID)
{
int const index = this->rowOfUserID(userID);
if (index < 0)
{
app().log().error(QString("Received toggleSplitModeFinished event for unknown userID %1").arg(userID));
return;
}
users_[index]->emitToggleSplitModeFinished();
}
//****************************************************************************************************************************************************
/// \return THe number of items in the list.
//****************************************************************************************************************************************************
int UserList::count() const
{
return users_.size();
}

View File

@ -0,0 +1,64 @@
// 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/>.
#ifndef BRIDGE_QT6_USER_LIST_H
#define BRIDGE_QT6_USER_LIST_H
#include "User.h"
//****************************************************************************************************************************************************
/// \brief User list class.
//****************************************************************************************************************************************************
class UserList: public QAbstractListModel
{
Q_OBJECT
public: // member functions.
explicit UserList(QObject *parent = nullptr); ///< Default constructor.
UserList(UserList const &other) = delete ; ///< Disabled copy-constructor.
UserList& operator=(UserList const& other) = delete; ///< Disabled assignment operator.
~UserList() override = default; ///< Destructor
void connectGRPCEvents() const; ///< Connects gRPC event to the model.
int rowCount(QModelIndex const &parent) const override; ///< Return the number of row in the model
QVariant data(QModelIndex const &index, int role) const override; ///< Retrieve model data.
void reset(); ///< Reset the user list.
void reset(QList<SPUser> const &users); ///< Replace the user list.
int rowOfUserID(QString const &userID) const;
void removeUserAt(int row); ///< Remove the user at a given row
void appendUser(SPUser const& user); ///< Add a new user.
void updateUserAtRow(int row, User const& user); ///< Update the user at given row.
// the count property.
Q_PROPERTY(int count READ count NOTIFY countChanged)
int count() const; ///< The count property getter.
signals:
void countChanged(int count); ///< Signal for the count property.
public:
Q_INVOKABLE User* get(int row) const;
public slots: ///< handler for signals coming from the gRPC service
void onUserChanged(QString const &userID);
void onToggleSplitModeFinished(QString const &userID);
private: // data members
QList<SPUser> users_; ///< The user list.
};
#endif // BRIDGE_QT6_USER_LIST_H

View File

@ -0,0 +1,103 @@
// 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/>.
#include "Pch.h"
#include "Overseer.h"
#include "Exception.h"
//****************************************************************************************************************************************************
/// \param[in] worker The worker.
/// \param[in] parent The parent object of the worker.
//****************************************************************************************************************************************************
Overseer::Overseer(Worker *worker, QObject *parent)
: QObject(parent)
, thread_(new QThread(parent))
, worker_(worker)
{
if (!worker_)
throw Exception("Overseer cannot accept a nil worker.");
}
//****************************************************************************************************************************************************
//
//****************************************************************************************************************************************************
Overseer::~Overseer()
{
this->release();
}
//****************************************************************************************************************************************************
/// \param[in] autorelease Should the overseer automatically release the worker and thread when done.
//****************************************************************************************************************************************************
void Overseer::startWorker(bool autorelease) const
{
if (!worker_)
throw Exception("Cannot start overseer with null worker.");
if (!thread_)
throw Exception("Cannot start overseer with null thread.");
worker_->moveToThread(thread_);
connect(thread_, &QThread::started, worker_, &Worker::run);
connect(worker_, &Worker::finished, thread_, &QThread::quit);
connect(worker_, &Worker::error, thread_, &QThread::quit);
if (autorelease)
{
connect(worker_, &Worker::error, this, &Overseer::release);
connect(worker_, &Worker::finished, this, &Overseer::release);
}
thread_->start();
}
//****************************************************************************************************************************************************
//
//****************************************************************************************************************************************************
void Overseer::release()
{
if (worker_)
{
worker_->deleteLater();
worker_ = nullptr;
}
if (thread_)
{
if (!thread_->isFinished())
{
thread_->quit();
thread_->wait();
}
thread_->deleteLater();
thread_ = nullptr;
}
}
//****************************************************************************************************************************************************
/// \return true iff the worker is finished, release
//****************************************************************************************************************************************************
bool Overseer::isFinished() const
{
if ((!worker_) || (!worker_->thread()))
return true;
return worker_->thread()->isFinished();
}

View File

@ -0,0 +1,55 @@
// 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/>.
#ifndef BRIDGE_QT6_OVERSEER_H
#define BRIDGE_QT6_OVERSEER_H
#include "Worker.h"
//****************************************************************************************************************************************************
/// \brief Overseer used to manager a worker instance and its associated thread.
//****************************************************************************************************************************************************
class Overseer: public QObject
{
Q_OBJECT
public: // member functions.
explicit Overseer(Worker* worker, QObject* parent); ///< Default constructor.
Overseer(Overseer const&) = delete; ///< Disabled copy-constructor.
Overseer(Overseer&&) = delete; ///< Disabled assignment copy-constructor.
~Overseer() override; ///< Destructor.
Overseer& operator=(Overseer const&) = delete; ///< Disabled assignment operator.
Overseer& operator=(Overseer&&) = delete; ///< Disabled move assignment operator.
bool isFinished() const; ///< Check if the worker is finished.
public slots:
void startWorker(bool autorelease) const; ///< Run the worker.
void release(); ///< Delete the worker and its thread.
public: // data members.
QThread *thread_ { nullptr }; ///< The thread.
Worker *worker_ { nullptr }; ///< The worker.
};
typedef std::unique_ptr<Overseer> UPOverseer; ///< Type definition for unique pointer to Overseer.
typedef std::shared_ptr<Overseer> SPOverseer; ///< Type definition for shared pointer to Overseer.
#endif //BRIDGE_QT6_OVERSEER_H

View File

@ -0,0 +1,46 @@
// 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/>.
#ifndef BRIDGE_QT6_WORKER_H
#define BRIDGE_QT6_WORKER_H
//****************************************************************************************************************************************************
/// \brief Pure virtual class for worker intended to perform a threaded operation.
//****************************************************************************************************************************************************
class Worker: public QObject
{
Q_OBJECT
public: // member functions
explicit Worker(QObject *parent) : QObject(parent) {} ///< Default constructor.
Worker(Worker const&) = delete; ///< Disabled copy-constructor.
Worker(Worker&&) = delete; ///< Disabled assignment copy-constructor.
~Worker() override = default; ///< Destructor.
Worker& operator=(Worker const&) = delete; ///< Disabled assignment operator.
Worker& operator=(Worker&&) = delete; ///< Disabled move assignment operator.
public slots:
virtual void run() = 0; ///< run the worker.
signals:
void started(); ///< Signal for the start of the worker
void finished(); ///< Signal for the end of the worker
void error(QString const& message); ///< Signal for errors. After an error, worker ends and finished is NOT emitted.
};
#endif //BRIDGE_QT6_WORKER_H

22
internal/frontend/qt6/build.sh Executable file
View File

@ -0,0 +1,22 @@
# 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/>.
#!/bin/bash
BUILD_DIR="./cmake-build-debug"
cmake -DCMAKE_BUILD_TYPE=Debug -G Ninja -S . -B ${BUILD_DIR}
ninja -C ${BUILD_DIR}

View File

@ -0,0 +1,135 @@
// 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/>.
#include "Pch.h"
#include "Exception.h"
#include "QMLBackend.h"
#include "Log.h"
#include "EventStreamWorker.h"
//****************************************************************************************************************************************************
/// // initialize the Qt application.
//****************************************************************************************************************************************************
std::shared_ptr<QGuiApplication> initQtApplication(int argc, char *argv[])
{
// Note the two following attributes must be set before instantiating the QCoreApplication/QGuiApplication class.
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling, false);
if (QSysInfo::productType() != "windows")
QCoreApplication::setAttribute(Qt::AA_UseSoftwareOpenGL);
QString const qsgInfo = QProcessEnvironment::systemEnvironment().value("QSG_INFO");
if ((!qsgInfo.isEmpty()) && (qsgInfo != "0"))
QLoggingCategory::setFilterRules("qt.scenegraph.general=true");
auto app = std::make_shared<QGuiApplication>(argc, argv);
/// \todo GODT-1670 Get version from go backend.
QGuiApplication::setApplicationName("Proton Mail Bridge");
QGuiApplication::setApplicationVersion("3.0");
QGuiApplication::setOrganizationName("Proton AG");
QGuiApplication::setOrganizationDomain("proton.ch");
QGuiApplication::setQuitOnLastWindowClosed(false);
return app;
}
//****************************************************************************************************************************************************
//
//****************************************************************************************************************************************************
void initLog()
{
Log &log = app().log();
log.setEchoInConsole(true);
log.setLevel(Log::Level::Debug);
}
//****************************************************************************************************************************************************
/// \param[in] engine The QML engine.
//****************************************************************************************************************************************************
QQmlComponent *createRootQmlComponent(QQmlApplicationEngine &engine)
{
/// \todo GODT-1669 pack QML and resources in QRC resource file.
QDir qmlDir("qml");
qmlRegisterType<QMLBackend>("CppBackend", 1, 0, "QMLBackend");
qmlRegisterType<UserList>("CppBackend", 1, 0, "UserList");
qmlRegisterType<User>("CppBackend", 1, 0, "User");
auto rootComponent = new QQmlComponent(&engine, &engine);
engine.addImportPath(qmlDir.absolutePath());
engine.addPluginPath(qmlDir.absolutePath());
QQuickStyle::addStylePath(qmlDir.absolutePath());
QQuickStyle::setStyle("Proton");
rootComponent->loadUrl(qmlDir.absoluteFilePath("Bridge.qml"));
if (rootComponent->status() != QQmlComponent::Status::Ready)
throw Exception("Could not load QML component");
return rootComponent;
}
//****************************************************************************************************************************************************
/// \param[in] argc The number of command-line arguments.
/// \param[in] argv The list of command-line arguments.
/// \return The exit code for the application.
//****************************************************************************************************************************************************
int main(int argc, char *argv[])
{
try
{
std::shared_ptr<QGuiApplication> guiApp = initQtApplication(argc, argv);
initLog();
/// \todo GODT-1667 Locate & Launch go backend (and wait for it).
app().backend().init();
QQmlApplicationEngine engine;
QQmlComponent *rootComponent = createRootQmlComponent(engine);
QObject *rootObject = rootComponent->beginCreate(engine.rootContext());
if (!rootObject)
throw Exception("Could not create root object.");
rootObject->setProperty("backend", QVariant::fromValue(&app().backend()));
rootComponent->completeCreate();
int result = QGuiApplication::exec();
app().log().info(QString("Exiting app with return code %1").arg(result));
app().grpc().stopEventStream();
app().backend().clearUserList();
/// \todo GODT-1667 shutdown go backend.
return result;
}
catch (Exception const &e)
{
app().log().error(e.qwhat());
return EXIT_FAILURE;
}
}

View File

@ -23,6 +23,7 @@ import QtQuick.Controls 2.12
import Proton 4.0 import Proton 4.0
import Notifications 1.0 import Notifications 1.0
import CppBackend 1.0
import "tests" import "tests"
@ -32,11 +33,14 @@ ApplicationWindow {
width: 960 width: 960
height: 576 height: 576
visible: true
minimumHeight: contentLayout.implicitHeight minimumHeight: contentLayout.implicitHeight
minimumWidth: contentLayout.implicitWidth minimumWidth: contentLayout.implicitWidth
colorScheme: ProtonStyle.currentStyle colorScheme: ProtonStyle.currentStyle
property var backend property var backend
property var notifications property var notifications

Some files were not shown because too many files have changed in this diff Show More