mirror of
https://github.com/ProtonMail/proton-bridge.git
synced 2025-12-10 04:36:43 +00:00
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:
4
.gitignore
vendored
4
.gitignore
vendored
@ -34,4 +34,6 @@ proton-bridge
|
||||
|
||||
# Jetbrains (CLion, Golang) cmake build dirs
|
||||
cmake-build-*/
|
||||
cmake-build-*/
|
||||
|
||||
# Doxygen doc files
|
||||
_doc/
|
||||
|
||||
@ -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-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)
|
||||
* [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)
|
||||
* [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)
|
||||
* [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)
|
||||
* [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)
|
||||
@ -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-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-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)
|
||||
* [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)
|
||||
* [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)
|
||||
@ -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)
|
||||
* [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)
|
||||
* [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)
|
||||
* [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)
|
||||
* [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)
|
||||
* [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)
|
||||
* [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)
|
||||
|
||||
11
go.mod
11
go.mod
@ -20,12 +20,10 @@ require (
|
||||
github.com/ProtonMail/go-srp v0.0.5
|
||||
github.com/ProtonMail/go-vcard v0.0.0-20180326232728-33aaa0a0c8a5
|
||||
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/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/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/messages-go/v16 v16.0.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-smtp v0.14.0
|
||||
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/flynn-archive/go-shlex v0.0.0-20150515145356-3f9db97f8568 // indirect
|
||||
github.com/getsentry/sentry-go v0.12.0
|
||||
github.com/go-resty/resty/v2 v2.6.0
|
||||
github.com/godbus/dbus v4.1.0+incompatible
|
||||
@ -53,15 +49,12 @@ require (
|
||||
github.com/keybase/go-keychain v0.0.0
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
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/nsf/jsondiff v0.0.0-20200515183724-f29ed568f4ce
|
||||
github.com/olekukonko/tablewriter v0.0.4 // indirect
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/prometheus/procfs v0.7.3 // indirect
|
||||
github.com/ricochet2200/go-disk-usage/du v0.0.0-20210707232629-ac9918953285
|
||||
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/therecipe/qt v0.0.0-20200701200531-7f61353ee73e
|
||||
github.com/urfave/cli/v2 v2.2.0
|
||||
@ -73,7 +66,7 @@ require (
|
||||
golang.org/x/text v0.3.7
|
||||
google.golang.org/grpc v1.46.2
|
||||
google.golang.org/protobuf v1.28.0
|
||||
howett.net/plist v1.0.0
|
||||
howett.net/plist v1.0.0 // indirect
|
||||
)
|
||||
|
||||
replace (
|
||||
|
||||
16
go.sum
16
go.sum
@ -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/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-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/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-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/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/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/go.mod h1:ZW1KxHNG6q5LMgFKf9Ap/d2eVYeyGf5+fAUEAjJWtmo=
|
||||
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-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-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-20200509203442-7bfe0ed36a21 h1:OJyUGMJTzHTd1XQp98QTaHernxMYzRaOasRir9hUlFQ=
|
||||
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/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/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/go.mod h1:NSap0JBYWzHND8oMbyi0+XZhUalc1TBdRL1M71JZW2c=
|
||||
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.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.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.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
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-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
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/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.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-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-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.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
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.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
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/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
|
||||
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.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM=
|
||||
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/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
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/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/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/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
|
||||
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
||||
|
||||
@ -31,7 +31,6 @@ import (
|
||||
"github.com/ProtonMail/proton-bridge/v2/internal/frontend"
|
||||
"github.com/ProtonMail/proton-bridge/v2/internal/frontend/types"
|
||||
"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/store"
|
||||
"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()
|
||||
}()
|
||||
|
||||
go func() {
|
||||
defer b.CrashHandler.HandlePanic()
|
||||
rpc.NewServer().ListenAndServe()
|
||||
}()
|
||||
|
||||
// We want to remove old versions if the app exits successfully.
|
||||
b.AddTeardownAction(b.Versioner.RemoveOldVersions)
|
||||
|
||||
@ -158,7 +152,7 @@ func mailLoop(b *base.Base, c *cli.Context) error { //nolint:funlen
|
||||
case c.Bool(flagNonInteractive):
|
||||
return <-(make(chan error)) // Block forever.
|
||||
default:
|
||||
frontendMode = "qt"
|
||||
frontendMode = "grpc"
|
||||
}
|
||||
|
||||
f := frontend.New(
|
||||
|
||||
@ -23,7 +23,7 @@ import (
|
||||
"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/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/locations"
|
||||
"github.com/ProtonMail/proton-bridge/v2/internal/updater"
|
||||
@ -39,7 +39,7 @@ type Frontend interface {
|
||||
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(
|
||||
version,
|
||||
buildVersion,
|
||||
@ -58,10 +58,9 @@ func New(
|
||||
) Frontend {
|
||||
bridgeWrap := types.NewBridgeWrap(bridge)
|
||||
switch frontendType {
|
||||
case "qt":
|
||||
return qt.New(
|
||||
case "grpc":
|
||||
return grpc.NewService(
|
||||
version,
|
||||
buildVersion,
|
||||
programName,
|
||||
showWindowOnStart,
|
||||
panicHandler,
|
||||
@ -74,6 +73,7 @@ func New(
|
||||
noEncConfirmator,
|
||||
restarter,
|
||||
)
|
||||
|
||||
case "cli":
|
||||
return cli.New(
|
||||
panicHandler,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -20,27 +20,21 @@ syntax = "proto3";
|
||||
import "google/protobuf/empty.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 BridgeRpc {
|
||||
service Bridge {
|
||||
|
||||
// App related calls
|
||||
rpc GetCursorPos (google.protobuf.Empty) returns (PointResponse); // May be unnecessary
|
||||
rpc GuiReady (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 SetShowOnStartup(google.protobuf.BoolValue) returns (google.protobuf.Empty);
|
||||
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 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 SetIsAutostartOn(google.protobuf.BoolValue) returns (google.protobuf.Empty);
|
||||
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 TriggerReset(google.protobuf.Empty) returns (google.protobuf.Empty);
|
||||
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 ReleaseNotesLink(google.protobuf.Empty) returns (google.protobuf.StringValue);
|
||||
rpc LandingPageLink(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 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 ColorSchemeName(google.protobuf.Empty) returns (google.protobuf.StringValue);
|
||||
rpc SetCurrentEmailClient(google.protobuf.StringValue) returns (google.protobuf.Empty);
|
||||
rpc ColorSchemeName(google.protobuf.Empty) returns (google.protobuf.StringValue); // TODO Color scheme should probably entirely be managed by the client.
|
||||
rpc CurrentEmailClient(google.protobuf.Empty) returns (google.protobuf.StringValue);
|
||||
rpc ReportBug(ReportBugRequest) returns (google.protobuf.Empty);
|
||||
|
||||
@ -72,9 +66,7 @@ service BridgeRpc {
|
||||
rpc IsAutomaticUpdateOn(google.protobuf.Empty) returns (google.protobuf.BoolValue);
|
||||
|
||||
// cache
|
||||
rpc SetIsCacheOnDiskEnabled (google.protobuf.BoolValue) returns (google.protobuf.Empty);
|
||||
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 ChangeLocalCache(ChangeLocalCacheRequest) returns (google.protobuf.Empty);
|
||||
|
||||
@ -84,9 +76,7 @@ service BridgeRpc {
|
||||
rpc SetUseSslForSmtp(google.protobuf.BoolValue) returns (google.protobuf.Empty);
|
||||
rpc UseSslForSmtp(google.protobuf.Empty) returns (google.protobuf.BoolValue);
|
||||
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 SetSmtpPort(google.protobuf.Int32Value) returns (google.protobuf.Empty);
|
||||
rpc SmtpPort(google.protobuf.Empty) returns (google.protobuf.Int32Value);
|
||||
rpc ChangePorts(ChangePortsRequest) returns (google.protobuf.Empty);
|
||||
rpc IsPortFree(google.protobuf.Int32Value) returns (google.protobuf.BoolValue);
|
||||
@ -98,28 +88,21 @@ service BridgeRpc {
|
||||
|
||||
// User & user list
|
||||
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 LogoutUser(google.protobuf.StringValue) returns (google.protobuf.Empty);
|
||||
rpc RemoveUser(google.protobuf.StringValue) returns (google.protobuf.Empty);
|
||||
rpc ConfigureUserAppleMail(ConfigureAppleMailRequest) returns (google.protobuf.Empty);
|
||||
|
||||
// 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
|
||||
//**********************************************************************************************************************
|
||||
|
||||
//**********************************************************
|
||||
// GUI related messages
|
||||
//**********************************************************
|
||||
message PointResponse {
|
||||
int32 x = 1;
|
||||
int32 y = 2;
|
||||
};
|
||||
|
||||
message ReportBugRequest {
|
||||
string description = 1;
|
||||
string address = 2;
|
||||
@ -214,7 +197,7 @@ message StreamEvent {
|
||||
message AppEvent {
|
||||
oneof event {
|
||||
InternetStatusEvent internetStatus = 1;
|
||||
AutostartFinishedEvent autostartFinished = 2;
|
||||
ToggleAutostartFinishedEvent toggleAutostartFinished = 2;
|
||||
ResetFinishedEvent resetFinished = 3;
|
||||
ReportBugFinishedEvent reportBugFinished = 4;
|
||||
ReportBugSuccessEvent reportBugSuccess = 5;
|
||||
@ -227,7 +210,7 @@ message InternetStatusEvent {
|
||||
bool connected = 1;
|
||||
}
|
||||
|
||||
message AutostartFinishedEvent {}
|
||||
message ToggleAutostartFinishedEvent {}
|
||||
message ResetFinishedEvent {}
|
||||
message ReportBugFinishedEvent {}
|
||||
message ReportBugSuccessEvent {}
|
||||
@ -243,6 +226,7 @@ message LoginEvent {
|
||||
LoginTfaRequestedEvent tfaRequested = 2;
|
||||
LoginTwoPasswordsRequestedEvent twoPasswordRequested = 3;
|
||||
LoginFinishedEvent finished = 4;
|
||||
LoginFinishedEvent alreadyLoggedIn = 5;
|
||||
}
|
||||
}
|
||||
|
||||
@ -268,7 +252,7 @@ message LoginTfaRequestedEvent {
|
||||
message LoginTwoPasswordsRequestedEvent {}
|
||||
|
||||
message LoginFinishedEvent {
|
||||
bool wasAlreadyLoggedIn = 1;
|
||||
string userID = 1;
|
||||
}
|
||||
|
||||
//**********************************************************
|
||||
@ -320,6 +304,8 @@ message CacheEvent {
|
||||
CacheErrorEvent error = 1;
|
||||
CacheLocationChangeSuccessEvent locationChangedSuccess = 2;
|
||||
ChangeLocalCacheFinishedEvent changeLocalCacheFinished = 3;
|
||||
IsCacheOnDiskEnabledChanged isCacheOnDiskEnabledChanged = 4;
|
||||
DiskCachePathChanged diskCachePathChanged = 5;
|
||||
}
|
||||
}
|
||||
|
||||
@ -337,6 +323,16 @@ message CacheLocationChangeSuccessEvent {};
|
||||
|
||||
message ChangeLocalCacheFinishedEvent {};
|
||||
|
||||
|
||||
message IsCacheOnDiskEnabledChanged {
|
||||
bool enabled = 1;
|
||||
}
|
||||
|
||||
message DiskCachePathChanged {
|
||||
string path = 1;
|
||||
}
|
||||
|
||||
|
||||
//**********************************************************
|
||||
// Mail settings related events
|
||||
//**********************************************************
|
||||
@ -393,11 +389,11 @@ message NoActiveKeyForRecipientEvent {
|
||||
}
|
||||
|
||||
message AddressChangedEvent {
|
||||
string address = 1; // TODO: user event ?
|
||||
string address = 1;
|
||||
}
|
||||
|
||||
message AddressChangedLogoutEvent {
|
||||
string address = 1; // TODO: user event ?
|
||||
string address = 1;
|
||||
}
|
||||
|
||||
message ApiCertIssueEvent {}
|
||||
@ -419,10 +415,10 @@ message ToggleSplitModeFinishedEvent {
|
||||
}
|
||||
|
||||
message UserDisconnectedEvent {
|
||||
string username = 1; // TODO: isn't it userID ?
|
||||
string username = 1;
|
||||
}
|
||||
|
||||
message UserChangedEvent {
|
||||
User user = 1;
|
||||
string userID = 1;
|
||||
}
|
||||
|
||||
1955
internal/frontend/grpc/bridge_grpc.pb.go
Normal file
1955
internal/frontend/grpc/bridge_grpc.pb.go
Normal file
File diff suppressed because it is too large
Load Diff
69
internal/frontend/grpc/certs.go
Normal file
69
internal/frontend/grpc/certs.go
Normal 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-----`
|
||||
)
|
||||
199
internal/frontend/grpc/event_factory.go
Normal file
199
internal/frontend/grpc/event_factory.go
Normal 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}}
|
||||
}
|
||||
329
internal/frontend/grpc/service.go
Normal file
329
internal/frontend/grpc/service.go
Normal 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.
|
||||
}
|
||||
543
internal/frontend/grpc/service_methods.go
Normal file
543
internal/frontend/grpc/service_methods.go
Normal 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
|
||||
}
|
||||
151
internal/frontend/grpc/service_stream.go
Normal file
151
internal/frontend/grpc/service_stream.go
Normal 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
|
||||
}
|
||||
132
internal/frontend/grpc/service_user.go
Normal file
132
internal/frontend/grpc/service_user.go
Normal 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
|
||||
}
|
||||
@ -15,29 +15,15 @@
|
||||
// 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
|
||||
package grpc
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/therecipe/qt/core"
|
||||
"github.com/therecipe/qt/gui"
|
||||
"github.com/ProtonMail/proton-bridge/v2/internal/frontend/types"
|
||||
)
|
||||
|
||||
// getCursorPos returns current mouse position to be able to use in QML
|
||||
func getCursorPos() *core.QPoint {
|
||||
return gui.QCursor_Pos()
|
||||
}
|
||||
|
||||
// newQByteArrayFromString is a wrapper for new QByteArray from string.
|
||||
func newQByteArrayFromString(name string) *core.QByteArray {
|
||||
return core.NewQByteArray2(name, len(name))
|
||||
}
|
||||
|
||||
var (
|
||||
reMultiSpaces = regexp.MustCompile(`\s{2,}`)
|
||||
reStartWithSymbol = regexp.MustCompile(`^[.,/#!$@%^&*;:{}=\-_` + "`" + `~()]`)
|
||||
@ -69,3 +55,19 @@ func getInitials(fullName string) string {
|
||||
}
|
||||
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(),
|
||||
}
|
||||
}
|
||||
@ -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()
|
||||
}
|
||||
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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()
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
@ -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() {}
|
||||
@ -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()
|
||||
}
|
||||
@ -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()
|
||||
}
|
||||
@ -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]
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
@ -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)
|
||||
})
|
||||
|
||||
}
|
||||
@ -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())
|
||||
}
|
||||
45
internal/frontend/qt6/AppController.cpp
Normal file
45
internal/frontend/qt6/AppController.cpp
Normal 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>())
|
||||
{
|
||||
|
||||
}
|
||||
59
internal/frontend/qt6/AppController.h
Normal file
59
internal/frontend/qt6/AppController.h
Normal 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
|
||||
136
internal/frontend/qt6/CMakeLists.txt
Normal file
136
internal/frontend/qt6/CMakeLists.txt
Normal 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()
|
||||
@ -15,13 +15,15 @@
|
||||
// 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 !darwin && build_qt
|
||||
// +build !darwin,build_qt
|
||||
|
||||
package dockicon
|
||||
#include "Pch.h"
|
||||
|
||||
func SetDockIconVisibleState(visible bool) {}
|
||||
|
||||
func GetDockIconVisibleState() bool {
|
||||
return true
|
||||
}
|
||||
#ifndef Q_OS_MACOS
|
||||
|
||||
|
||||
void setDockIconVisibleState(bool visible) { Q_UNUSED(visible) }
|
||||
bool getDockIconVisibleState() { return true; }
|
||||
|
||||
|
||||
#endif
|
||||
@ -15,22 +15,13 @@
|
||||
// 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/>.
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifndef LOGRUS_QML_LOG_H
|
||||
#define LOGRUS_QML_LOG_H
|
||||
#ifndef BRIDGE_QT6_DOCK_ICON_H
|
||||
#define BRIDGE_QT6_DOCK_ICON_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif // C++
|
||||
void setDockIconVisibleState(bool visible); ///< Set the DOCK icon visibility state
|
||||
bool getDockIconVisibleState(); ///< Get the Dock icon visibility state
|
||||
|
||||
void InstallMessageHandler();
|
||||
;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif // C++
|
||||
|
||||
#endif // LOGRUS_QML_LOG_H
|
||||
#endif // #ifndef BRIDGE_QT6_DOCK_ICON_H
|
||||
@ -15,13 +15,16 @@
|
||||
// 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 darwin
|
||||
// +build build_qt
|
||||
|
||||
#include "DockIcon.h"
|
||||
#include "Pch.h"
|
||||
#include <Cocoa/Cocoa.h>
|
||||
#include "DockIcon.h"
|
||||
|
||||
void SetDockIconVisibleState(bool visible) {
|
||||
|
||||
#ifdef Q_OS_MACOS
|
||||
|
||||
|
||||
void setDockIconVisibleState(bool visible) {
|
||||
if (visible) {
|
||||
[NSApp setActivationPolicy: NSApplicationActivationPolicyRegular];
|
||||
return;
|
||||
@ -31,12 +34,16 @@ void SetDockIconVisibleState(bool visible) {
|
||||
}
|
||||
}
|
||||
|
||||
bool GetDockIconVisibleState() {
|
||||
|
||||
bool getDockIconVisibleState() {
|
||||
switch ([NSApp activationPolicy]) {
|
||||
case NSApplicationActivationPolicyAccessory:
|
||||
case NSApplicationActivationPolicyProhibited:
|
||||
return false;
|
||||
return false;
|
||||
case NSApplicationActivationPolicyRegular:
|
||||
return true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endif // #ifdef Q_OS_MACOS
|
||||
2656
internal/frontend/qt6/Doxyfile
Normal file
2656
internal/frontend/qt6/Doxyfile
Normal file
File diff suppressed because it is too large
Load Diff
57
internal/frontend/qt6/EventStreamWorker.cpp
Normal file
57
internal/frontend/qt6/EventStreamWorker.cpp
Normal 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());
|
||||
}
|
||||
}
|
||||
52
internal/frontend/qt6/EventStreamWorker.h
Normal file
52
internal/frontend/qt6/EventStreamWorker.h
Normal 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
|
||||
68
internal/frontend/qt6/Exception.cpp
Normal file
68
internal/frontend/qt6/Exception.cpp
Normal 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();
|
||||
}
|
||||
46
internal/frontend/qt6/Exception.h
Normal file
46
internal/frontend/qt6/Exception.h
Normal 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
|
||||
1183
internal/frontend/qt6/GRPC/GRPCClient.cpp
Normal file
1183
internal/frontend/qt6/GRPC/GRPCClient.cpp
Normal file
File diff suppressed because it is too large
Load Diff
214
internal/frontend/qt6/GRPC/GRPCClient.h
Normal file
214
internal/frontend/qt6/GRPC/GRPCClient.h
Normal 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
|
||||
63
internal/frontend/qt6/GRPC/GRPCUtils.cpp
Normal file
63
internal/frontend/qt6/GRPC/GRPCUtils.cpp
Normal 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;
|
||||
}
|
||||
@ -15,20 +15,16 @@
|
||||
// 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 darwin && build_qt
|
||||
// +build darwin,build_qt
|
||||
|
||||
package dockicon
|
||||
#ifndef BRIDGE_QT6_GRPCUTILS_H
|
||||
#define BRIDGE_QT6_GRPCUTILS_H
|
||||
|
||||
// #cgo CFLAGS: -x objective-c
|
||||
// #cgo LDFLAGS: -framework Cocoa
|
||||
// #include "DockIcon.h"
|
||||
import "C"
|
||||
#include "GRPC/bridge.grpc.pb.h"
|
||||
#include "grpc++/grpc++.h"
|
||||
#include "User/User.h"
|
||||
|
||||
func SetDockIconVisibleState(visible bool) {
|
||||
C.SetDockIconVisibleState(C.bool(visible))
|
||||
}
|
||||
|
||||
func GetDockIconVisibleState() bool {
|
||||
return bool(C.GetDockIconVisibleState())
|
||||
}
|
||||
void logGRPCCallStatus(grpc::Status const& status, QString const &callName); ///< Log the status of a gRPC code.
|
||||
SPUser parsegrpcUser(grpc::User const& grpcUser); ///< Parse a gRPC user struct and return a User.
|
||||
|
||||
#endif // BRIDGE_QT6_GRPCUTILS_H
|
||||
2179
internal/frontend/qt6/GRPC/bridge.grpc.pb.cc
Normal file
2179
internal/frontend/qt6/GRPC/bridge.grpc.pb.cc
Normal file
File diff suppressed because it is too large
Load Diff
8146
internal/frontend/qt6/GRPC/bridge.grpc.pb.h
Normal file
8146
internal/frontend/qt6/GRPC/bridge.grpc.pb.h
Normal file
File diff suppressed because it is too large
Load Diff
12646
internal/frontend/qt6/GRPC/bridge.pb.cc
Normal file
12646
internal/frontend/qt6/GRPC/bridge.pb.cc
Normal file
File diff suppressed because it is too large
Load Diff
14902
internal/frontend/qt6/GRPC/bridge.pb.h
Normal file
14902
internal/frontend/qt6/GRPC/bridge.pb.h
Normal file
File diff suppressed because it is too large
Load Diff
159
internal/frontend/qt6/Log.cpp
Normal file
159
internal/frontend/qt6/Log.cpp
Normal 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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
74
internal/frontend/qt6/Log.h
Normal file
74
internal/frontend/qt6/Log.h
Normal 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
|
||||
@ -15,10 +15,9 @@
|
||||
// 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 darwin
|
||||
// +build build_qt
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
void SetDockIconVisibleState(bool visible);
|
||||
bool GetDockIconVisibleState();
|
||||
#include <QtCore>
|
||||
#include <QtQuick>
|
||||
#include <QtQml>
|
||||
#include <QtQuickControls2>
|
||||
#include <AppController.h>
|
||||
316
internal/frontend/qt6/QMLBackend.cpp
Normal file
316
internal/frontend/qt6/QMLBackend.cpp
Normal 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__));
|
||||
}
|
||||
222
internal/frontend/qt6/QMLBackend.h
Normal file
222
internal/frontend/qt6/QMLBackend.h
Normal 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
|
||||
95
internal/frontend/qt6/User/User.cpp
Normal file
95
internal/frontend/qt6/User/User.cpp
Normal 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();
|
||||
}
|
||||
93
internal/frontend/qt6/User/User.h
Normal file
93
internal/frontend/qt6/User/User.h
Normal 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
|
||||
220
internal/frontend/qt6/User/UserList.cpp
Normal file
220
internal/frontend/qt6/User/UserList.cpp
Normal 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();
|
||||
}
|
||||
64
internal/frontend/qt6/User/UserList.h
Normal file
64
internal/frontend/qt6/User/UserList.h
Normal 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
|
||||
103
internal/frontend/qt6/Worker/Overseer.cpp
Normal file
103
internal/frontend/qt6/Worker/Overseer.cpp
Normal 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();
|
||||
}
|
||||
55
internal/frontend/qt6/Worker/Overseer.h
Normal file
55
internal/frontend/qt6/Worker/Overseer.h
Normal 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
|
||||
46
internal/frontend/qt6/Worker/Worker.h
Normal file
46
internal/frontend/qt6/Worker/Worker.h
Normal 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
22
internal/frontend/qt6/build.sh
Executable 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}
|
||||
135
internal/frontend/qt6/main.cpp
Normal file
135
internal/frontend/qt6/main.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -23,6 +23,7 @@ import QtQuick.Controls 2.12
|
||||
|
||||
import Proton 4.0
|
||||
import Notifications 1.0
|
||||
import CppBackend 1.0
|
||||
|
||||
import "tests"
|
||||
|
||||
@ -32,11 +33,14 @@ ApplicationWindow {
|
||||
width: 960
|
||||
height: 576
|
||||
|
||||
visible: true
|
||||
|
||||
minimumHeight: contentLayout.implicitHeight
|
||||
minimumWidth: contentLayout.implicitWidth
|
||||
|
||||
colorScheme: ProtonStyle.currentStyle
|
||||
|
||||
|
||||
property var backend
|
||||
property var notifications
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user