diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 6841984f..5cf27441 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -32,7 +32,7 @@ stages: .rules-branch-and-MR-always: rules: - - if: $CI_COMMIT_BRANCH || $CI_PIPELINE_SOURCE == "merge_request_event" + - if: $CI_COMMIT_BRANCH || $CI_PIPELINE_SOURCE == "merge_request_event" when: always allow_failure: false - when: never @@ -54,6 +54,28 @@ stages: allow_failure: true - when: never +.rules-branch-manual-MR-and-devel-always: + rules: + - if: $CI_COMMIT_BRANCH == "devel" || $CI_PIPELINE_SOURCE == "merge_request_event" + when: always + allow_failure: false + - if: $CI_COMMIT_BRANCH + when: manual + allow_failure: true + - when: never + +.after-script-code-coverage: + after_script: + - go get github.com/boumenot/gocover-cobertura + - go run github.com/boumenot/gocover-cobertura < /tmp/coverage.out > coverage.xml + - "go tool cover -func=/tmp/coverage.out | grep total:" + coverage: '/total:.*\(statements\).*\d+\.\d+%/' + artifacts: + reports: + coverage_report: + coverage_format: cobertura + path: coverage.xml + # Stage: TEST lint: @@ -68,7 +90,8 @@ lint: test-linux: stage: test extends: - - .rules-branch-manual-MR-always + - .rules-branch-manual-MR-and-devel-always + - .after-script-code-coverage script: - make test tags: @@ -106,6 +129,8 @@ dependency-updates: script: - make updates + + # Stage: BUILD .build-base: diff --git a/.golangci.yml b/.golangci.yml index 9ac51197..6149b688 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -23,7 +23,6 @@ issues: - path: _test\.go linters: - dupl - - funlen - gochecknoglobals - gochecknoinits - gosec @@ -32,7 +31,6 @@ issues: - path: test linters: - dupl - - funlen - gochecknoglobals - gochecknoinits - gosec @@ -64,7 +62,6 @@ linters: - depguard # Go linter that checks if package imports are in a list of acceptable packages [fast: true, auto-fix: false] - dogsled # Checks assignments with too many blank identifiers (e.g. x, _, _, _, := f()) [fast: true, auto-fix: false] - dupl # Tool for code clone detection [fast: true, auto-fix: false] - - funlen # Tool for detection of long functions [fast: true, auto-fix: false] - gochecknoglobals # Checks that no globals are present in Go code [fast: true, auto-fix: false] - gochecknoinits # Checks that no init functions are present in Go code [fast: true, auto-fix: false] - goconst # Finds repeated strings that could be replaced by a constant [fast: true, auto-fix: false] diff --git a/COPYING_NOTES.md b/COPYING_NOTES.md index 2269bf30..171fa8fd 100644 --- a/COPYING_NOTES.md +++ b/COPYING_NOTES.md @@ -53,6 +53,7 @@ Proton Mail Bridge includes the following 3rd party software: * [html2text](https://github.com/jaytaylor/html2text) available under [license](https://github.com/jaytaylor/html2text/blob/master/LICENSE) * [go-keychain](https://github.com/keybase/go-keychain) available under [license](https://github.com/keybase/go-keychain/blob/master/LICENSE) * [dns](https://github.com/miekg/dns) available under [license](https://github.com/miekg/dns/blob/master/LICENSE) +* [memory](https://github.com/pbnjay/memory) available under [license](https://github.com/pbnjay/memory/blob/master/LICENSE) * [errors](https://github.com/pkg/errors) available under [license](https://github.com/pkg/errors/blob/master/LICENSE) * [profile](https://github.com/pkg/profile) available under [license](https://github.com/pkg/profile/blob/master/LICENSE) * [logrus](https://github.com/sirupsen/logrus) available under [license](https://github.com/sirupsen/logrus/blob/master/LICENSE) @@ -78,6 +79,8 @@ Proton Mail Bridge includes the following 3rd party software: * [cascadia](https://github.com/andybalholm/cascadia) available under [license](https://github.com/andybalholm/cascadia/blob/master/LICENSE) * [antlr](https://github.com/antlr/antlr4/runtime/Go/antlr) available under [license](https://github.com/antlr/antlr4/runtime/Go/antlr/blob/master/LICENSE) * [go-textseg](https://github.com/apparentlymart/go-textseg/v13) available under [license](https://github.com/apparentlymart/go-textseg/v13/blob/master/LICENSE) +* [sonic](https://github.com/bytedance/sonic) available under [license](https://github.com/bytedance/sonic/blob/master/LICENSE) +* [base64x](https://github.com/chenzhuoyu/base64x) available under [license](https://github.com/chenzhuoyu/base64x/blob/master/LICENSE) * [test](https://github.com/chzyer/test) available under [license](https://github.com/chzyer/test/blob/master/LICENSE) * [circl](https://github.com/cloudflare/circl) available under [license](https://github.com/cloudflare/circl/blob/master/LICENSE) * [go-md2man](https://github.com/cpuguy83/go-md2man/v2) available under [license](https://github.com/cpuguy83/go-md2man/v2/blob/master/LICENSE) @@ -88,6 +91,7 @@ Proton Mail Bridge includes the following 3rd party software: * [go-windows](https://github.com/elastic/go-windows) available under [license](https://github.com/elastic/go-windows/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) +* [fgprof](https://github.com/felixge/fgprof) available under [license](https://github.com/felixge/fgprof/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) * [sse](https://github.com/gin-contrib/sse) available under [license](https://github.com/gin-contrib/sse/blob/master/LICENSE) * [gin](https://github.com/gin-gonic/gin) available under [license](https://github.com/gin-gonic/gin/blob/master/LICENSE) @@ -97,6 +101,7 @@ Proton Mail Bridge includes the following 3rd party software: * [validator](https://github.com/go-playground/validator/v10) available under [license](https://github.com/go-playground/validator/v10/blob/master/LICENSE) * [uuid](https://github.com/gofrs/uuid) available under [license](https://github.com/gofrs/uuid/blob/master/LICENSE) * [protobuf](https://github.com/golang/protobuf) available under [license](https://github.com/golang/protobuf/blob/master/LICENSE) +* [pprof](https://github.com/google/pprof) available under [license](https://github.com/google/pprof/blob/master/LICENSE) * [errwrap](https://github.com/hashicorp/errwrap) available under [license](https://github.com/hashicorp/errwrap/blob/master/LICENSE) * [go-immutable-radix](https://github.com/hashicorp/go-immutable-radix) available under [license](https://github.com/hashicorp/go-immutable-radix/blob/master/LICENSE) * [go-memdb](https://github.com/hashicorp/go-memdb) available under [license](https://github.com/hashicorp/go-memdb/blob/master/LICENSE) @@ -104,6 +109,7 @@ Proton Mail Bridge includes the following 3rd party software: * [hcl](https://github.com/hashicorp/hcl/v2) available under [license](https://github.com/hashicorp/hcl/v2/blob/master/LICENSE) * [multierror](https://github.com/joeshaw/multierror) available under [license](https://github.com/joeshaw/multierror/blob/master/LICENSE) * [go](https://github.com/json-iterator/go) available under [license](https://github.com/json-iterator/go/blob/master/LICENSE) +* [cpuid](https://github.com/klauspost/cpuid/v2) available under [license](https://github.com/klauspost/cpuid/v2/blob/master/LICENSE) * [go-urn](https://github.com/leodido/go-urn) available under [license](https://github.com/leodido/go-urn/blob/master/LICENSE) * [go-colorable](https://github.com/mattn/go-colorable) available under [license](https://github.com/mattn/go-colorable/blob/master/LICENSE) * [go-isatty](https://github.com/mattn/go-isatty) available under [license](https://github.com/mattn/go-isatty/blob/master/LICENSE) @@ -114,22 +120,24 @@ Proton Mail Bridge includes the following 3rd party software: * [reflect2](https://github.com/modern-go/reflect2) available under [license](https://github.com/modern-go/reflect2/blob/master/LICENSE) * [tablewriter](https://github.com/olekukonko/tablewriter) available under [license](https://github.com/olekukonko/tablewriter/blob/master/LICENSE) * [go-toml](https://github.com/pelletier/go-toml/v2) available under [license](https://github.com/pelletier/go-toml/v2/blob/master/LICENSE) +* [lz4](https://github.com/pierrec/lz4/v4) available under [license](https://github.com/pierrec/lz4/v4/blob/master/LICENSE) * [go-difflib](https://github.com/pmezard/go-difflib) available under [license](https://github.com/pmezard/go-difflib/blob/master/LICENSE) * [procfs](https://github.com/prometheus/procfs) available under [license](https://github.com/prometheus/procfs/blob/master/LICENSE) * [uniseg](https://github.com/rivo/uniseg) available under [license](https://github.com/rivo/uniseg/blob/master/LICENSE) * [blackfriday](https://github.com/russross/blackfriday/v2) available under [license](https://github.com/russross/blackfriday/v2/blob/master/LICENSE) * [pflag](https://github.com/spf13/pflag) available under [license](https://github.com/spf13/pflag/blob/master/LICENSE) * [bom](https://github.com/ssor/bom) available under [license](https://github.com/ssor/bom/blob/master/LICENSE) +* [golang-asm](https://github.com/twitchyliquid64/golang-asm) available under [license](https://github.com/twitchyliquid64/golang-asm/blob/master/LICENSE) * [codec](https://github.com/ugorji/go/codec) available under [license](https://github.com/ugorji/go/codec/blob/master/LICENSE) * [tagparser](https://github.com/vmihailenco/tagparser/v2) available under [license](https://github.com/vmihailenco/tagparser/v2/blob/master/LICENSE) * [smetrics](https://github.com/xrash/smetrics) available under [license](https://github.com/xrash/smetrics/blob/master/LICENSE) * [go-cty](https://github.com/zclconf/go-cty) available under [license](https://github.com/zclconf/go-cty/blob/master/LICENSE) +* [arch](https://golang.org/x/arch) available under [license](https://cs.opensource.google/go/x/arch/+/master:LICENSE) * [crypto](https://golang.org/x/crypto) available under [license](https://cs.opensource.google/go/x/crypto/+/master:LICENSE) * [mod](https://golang.org/x/mod) available under [license](https://cs.opensource.google/go/x/mod/+/master:LICENSE) * [sync](https://golang.org/x/sync) available under [license](https://cs.opensource.google/go/x/sync/+/master:LICENSE) * [tools](https://golang.org/x/tools) available under [license](https://cs.opensource.google/go/x/tools/+/master:LICENSE) * [genproto](https://google.golang.org/genproto) -gopkg.in/yaml.v2 gopkg.in/yaml.v3 * [docker-credential-helpers](https://github.com/ProtonMail/docker-credential-helpers) available under [license](https://github.com/ProtonMail/docker-credential-helpers/blob/master/LICENSE) * [go-message](https://github.com/ProtonMail/go-message) available under [license](https://github.com/ProtonMail/go-message/blob/master/LICENSE) diff --git a/Changelog.md b/Changelog.md index 9ab01fd6..ac3c48f6 100644 --- a/Changelog.md +++ b/Changelog.md @@ -2,6 +2,65 @@ Changelog [format](http://keepachangelog.com/en/1.0.0/) +## [Bridge 3.1.0] Quebec + +### Changed +* GODT-2224: Refactor bridge sync to use less memory. +* GODT-2448: Supported Answered flag. +* GODT-2382: Added bridge-gui settings file with 'UseSoftwareRenderer' value. +* GODT-2411: allow qmake executable to be named qmake6. +* GODT-2273: Menu with "Close window" and "Quit Bridge" button in main window. +* GODT-2261: Sync progress in GUI. +* GODT-2385: Gluon cache fallback. +* GODT-2366: Handle failed message updates as creates. +* GODT-2201: Bump Gluon to use pure Go IMAP parser. +* GODT-2374: Import TLS certs via shell. +* GODT-2361: Bump GPA to use simple encrypter. +* GODT-1264: Constraint on Scheduled mailbox in connector + Integration tests. +* GODT-1264: Creation and visibility of the 'Scheduled' system label. +* GODT-2283: Limit max import size to 30MB (bump GPA to v0.4.0). +* GODT-2352: Only copy resource file when needed. +* GODT-2352: Use go-build-finalize macro to build vault-editor for both mac arch. +* GODT-2278: Properly override server_name for go. +* GODT-2255: Randomize the focus service port. +* GODT-2144: Handle IMAP/SMTP server errors via event stream. +* GODT-2144: Delay IMAP/SMTP server start until all users are loaded. +* GODT-2295: Notifications for IMAP login when signed out. +* GODT-2278: Improve sentry logs. +* GODT-2289: UIDValidity as Timestamp. + +### Fixed +* GODT-2424: Sync Builder Message Split. +* GODT-2419: Use connector.ErrOperationNotAllowed. +* GODT-2418: Ensure child folders are updated when parent is. +* GODT-1945: Handle disabled addresses correctly. +* GODT-2390: Add reports for uncaught json and net.opErr. +* GODT-2393: Improved handling of unrecoverable error. +* GODT-2394: Bump Gluon for golang.org/x/text DoS risk. +* GODT-2387: Ensure vault can be unlocked after factory reset. +* GODT-2389: Close bridge on exception and add max termination wait time. +* GODT-2201: Add missing rfc5322.CharsetReader initialization. +* GODT-1804: Preserve MIME parameters when uploading attachments. +* GODT-2312: Used space is properly updated. +* GODT-2319: Seed the math/rand RNG on app startup. +* GODT-2272: Use shorter filename for gRPC file socket. +* GODT-2318: Remove gluon DB if label sync was incomplete. +* GODT-2326: Only run sync after addIMAPUser(). +* GODT-2323: Fix Expunge not issued for move. +* GODT-2224: Properly handle context cancellation during sync. +* GODT-2328: Ignore labels that aren't part of user label set. +* GODT-2326: Fix potential Win32 API deadlock. +* GODT-1804: Only promote content headers if non-empty. +* GODT-2327: Remove unnecessary sync when changing address mode. +* GODT-2343: Only poll after send if sync is complete. +* GODT-2336: Recover from changed address order while bridge is down. +* GODT-2347: Prevent updates from being dropped if goroutine doesn't start fast. +* GODT-2351: Bump GPA to properly handle net.OpError and add tests. +* GODT-2351: Bump GPA to automatically retry on net.OpError. +* GODT-2365: Use predictable remote ID for placeholder mailboxes. +* GODT-2381: Unset draft flag on sent messages. +* GODT-2380: Only set external ID in header if non-empty. + ## [Bridge 3.0.20] Perth Narrows ### Added diff --git a/Makefile b/Makefile index 3ae6744f..c08dfb35 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ ROOT_DIR:=$(shell dirname $(realpath $(firstword $(MAKEFILE_LIST)))) .PHONY: build build-gui build-nogui build-launcher versioner hasher # Keep version hardcoded so app build works also without Git repository. -BRIDGE_APP_VERSION?=3.0.20+git +BRIDGE_APP_VERSION?=3.1.0+git APP_VERSION:=${BRIDGE_APP_VERSION} APP_FULL_NAME:=Proton Mail Bridge APP_VENDOR:=Proton AG @@ -100,9 +100,9 @@ endif ifeq "${GOOS}" "windows" go-build-finalize= \ - powershell Copy-Item ${ROOT_DIR}/${RESOURCE_FILE} ${4} && \ - $(call go-build,$(1),$(2),$(3)) && \ - powershell Remove-Item ${4} -Force + $(if $(4),powershell Copy-Item ${ROOT_DIR}/${RESOURCE_FILE} ${4} &&,) \ + $(call go-build,$(1),$(2),$(3)) \ + $(if $(4), && powershell Remove-Item ${4} -Force,) endif ${EXE_NAME}: gofiles ${RESOURCE_FILE} @@ -116,7 +116,7 @@ versioner: go build ${BUILD_FLAGS} -o versioner utils/versioner/main.go vault-editor: - go build -tags debug -o vault-editor utils/vault-editor/main.go + $(call go-build-finalize,"-tags=debug","vault-editor","./utils/vault-editor/main.go") hasher: go build -o hasher utils/hasher/main.go @@ -228,13 +228,13 @@ change-copyright-year: ./utils/missing_license.sh change-year test: gofiles - go test -v -timeout=5m -p=1 -count=1 -coverprofile=/tmp/coverage.out -run=${TESTRUN} ./internal/... ./pkg/... + go test -v -timeout=10m -p=1 -count=1 -coverprofile=/tmp/coverage.out -run=${TESTRUN} ./internal/... ./pkg/... test-race: gofiles go test -v -timeout=30m -p=1 -count=1 -race -failfast -run=${TESTRUN} ./internal/... ./pkg/... test-integration: gofiles - go test -v -timeout=10m -p=1 -count=1 github.com/ProtonMail/proton-bridge/v3/tests + go test -v -timeout=20m -p=1 -count=1 github.com/ProtonMail/proton-bridge/v3/tests test-integration-debug: gofiles dlv test github.com/ProtonMail/proton-bridge/v3/tests -- -test.v -test.timeout=10m -test.parallel=1 -test.count=1 diff --git a/README.md b/README.md index c6903c55..0fd5c853 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # Proton Mail Bridge and Import Export app -Copyright (c) 2022 Proton AG +Copyright (c) 2023 Proton AG This repository holds the Proton Mail Bridge and the Proton Mail Import-Export applications. For a detailed build information see [BUILDS](./BUILDS.md). @@ -48,9 +48,6 @@ major problems. ## Environment Variables -### Bridge application -- `BRIDGESTRICTMODE`: tells bridge to turn on `bbolt`'s "strict mode" which checks the database after every `Commit`. Set to `1` to enable. - ### Dev build or run - `APP_VERSION`: set the bridge app version used during testing or building - `PROTONMAIL_ENV`: when set to `dev` it is not using Sentry to report crashes @@ -70,25 +67,26 @@ There are now three types of system folders which Bridge recognises: |--------|-------------------------------------|-----------------------------------------------------|-------------------------------------|---------------------------------------| | config | %APPDATA%\protonmail\bridge-v3 | ~/Library/Application Support/protonmail/bridge-v3 | ~/.config/protonmail/bridge-v3 | $XDG_CONFIG_HOME/protonmail/bridge-v3 | | cache | %LOCALAPPDATA%\protonmail\bridge-v3 | ~/Library/Caches/protonmail/bridge-v3 | ~/.cache/protonmail/bridge-v3 | $XDG_CACHE_HOME/protonmail/bridge-v3 | -| data | %APPDATA%\protonmail\bridge-v3 | ~/Library/Application Support/protonmail/bridge-v3 | ~/.local/share/protonmail/bridge-v3 | $XDG_DATA_HOME/protonmail/bridge-v3 | +| data | %APPDATA%\protonmail\bridge-v3 | ~/Library/Application Support/protonmail/bridge-v3 | ~/.local/share/protonmail/bridge-v3 | $XDG_DATA_HOME/protonmail/bridge-v3 | | temp | %LOCALAPPDATA%\Temp | $TMPDIR if non-empty, else /tmp | $TMPDIR if non-empty, else /tmp | $TMPDIR if non-empty, else /tmp | ## Files -| | Base Dir | Path | -|-----------------------|----------|----------------------------| -| bridge lock file | cache | bridge.lock | -| bridge-gui lock file | cache | bridge-gui.lock | -| vault | config | vault.enc | -| gRPC server json | config | grpcServerConfig.json | -| gRPC client json | config | grpcClientConfig_.json | -| Logs | data | logs | -| gluon DB | data | gluon/backend/db | -| gluon messages | sata | gluon/backend/store | -| Update files | data | updates | -| sentry cache | data | sentry_cache | -| Mac/Linux File Socket | temp | bridge_{RANDOM_UUID}.sock | +| | Base Dir | Path | +|------------------------|----------|----------------------------| +| bridge lock file | cache | bridge.lock | +| bridge-gui lock file | cache | bridge-gui.lock | +| vault | config | vault.enc | +| gRPC server json | config | grpcServerConfig.json | +| gRPC client json | config | grpcClientConfig_.json | +| gRPC Focus server json | config | grpcFocusServerConfig.json | +| Logs | data | logs | +| gluon DB | data | gluon/backend/db | +| gluon messages | data | gluon/backend/store | +| Update files | data | updates | +| sentry cache | data | sentry_cache | +| Mac/Linux File Socket | temp | bridge{4_DIGITS} | diff --git a/go.mod b/go.mod index 940fdde6..1f0703ab 100644 --- a/go.mod +++ b/go.mod @@ -4,21 +4,21 @@ go 1.18 require ( github.com/0xAX/notificator v0.0.0-20220220101646-ee9b8921e557 - github.com/Masterminds/semver/v3 v3.1.1 - github.com/ProtonMail/gluon v0.14.2-0.20230309105237-65db9f3ab739 + github.com/Masterminds/semver/v3 v3.2.0 + github.com/ProtonMail/gluon v0.15.1-0.20230310125443-f755e8ce082a github.com/ProtonMail/go-autostart v0.0.0-20210130080809-00ed301c8e9a - github.com/ProtonMail/go-proton-api v0.3.1-0.20230308164916-42e487b4ad74 + github.com/ProtonMail/go-proton-api v0.4.1-0.20230313102028-4da9318e5f77 github.com/ProtonMail/go-rfc5322 v0.11.0 - github.com/ProtonMail/gopenpgp/v2 v2.4.10 - github.com/PuerkitoBio/goquery v1.8.0 + github.com/ProtonMail/gopenpgp/v2 v2.5.2 + github.com/PuerkitoBio/goquery v1.8.1 github.com/abiosoft/ishell v2.0.0+incompatible github.com/allan-simon/go-singleinstance v0.0.0-20210120080615-d0997106ab37 - github.com/bradenaw/juniper v0.8.0 + github.com/bradenaw/juniper v0.10.2 github.com/cucumber/godog v0.12.5 github.com/cucumber/messages-go/v16 v16.0.1 github.com/docker/docker-credential-helpers v0.6.3 github.com/elastic/go-sysinfo v1.8.1 - github.com/emersion/go-imap v1.2.1-0.20220429085312-746087b7a317 + github.com/emersion/go-imap v1.2.1 github.com/emersion/go-imap-id v0.0.0-20190926060100-f94a56b9ecde github.com/emersion/go-message v0.16.0 github.com/emersion/go-sasl v0.0.0-20220912192320-0145f2c60ead @@ -26,7 +26,7 @@ require ( github.com/fatih/color v1.13.0 github.com/getsentry/sentry-go v0.15.0 github.com/go-resty/resty/v2 v2.7.0 - github.com/goccy/go-json v0.9.11 + github.com/goccy/go-json v0.10.0 github.com/godbus/dbus v4.1.0+incompatible github.com/golang/mock v1.6.0 github.com/google/go-cmp v0.5.9 @@ -35,36 +35,39 @@ require ( github.com/jaytaylor/html2text v0.0.0-20211105163654-bc68cce691ba github.com/keybase/go-keychain v0.0.0 github.com/miekg/dns v1.1.50 + github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 github.com/pkg/errors v0.9.1 - github.com/pkg/profile v1.6.0 + github.com/pkg/profile v1.7.0 github.com/sirupsen/logrus v1.9.0 - github.com/stretchr/testify v1.8.0 - github.com/urfave/cli/v2 v2.20.3 + github.com/stretchr/testify v1.8.1 + github.com/urfave/cli/v2 v2.24.4 github.com/vmihailenco/msgpack/v5 v5.3.5 - go.uber.org/goleak v1.2.0 - golang.org/x/exp v0.0.0-20221023144134-a1e5550cf13e - golang.org/x/net v0.1.0 - golang.org/x/sys v0.1.0 - golang.org/x/text v0.4.0 - google.golang.org/grpc v1.50.1 + go.uber.org/goleak v1.2.1 + golang.org/x/exp v0.0.0-20230213192124-5e25df0256eb + golang.org/x/net v0.7.0 + golang.org/x/sys v0.5.0 + golang.org/x/text v0.7.0 + google.golang.org/grpc v1.53.0 google.golang.org/protobuf v1.28.1 howett.net/plist v1.0.0 ) require ( - ariga.io/atlas v0.7.0 // indirect - entgo.io/ent v0.11.2 // indirect + ariga.io/atlas v0.9.1-0.20230119145809-92243f7c55cb // indirect + entgo.io/ent v0.11.8 // indirect github.com/ProtonMail/bcrypt v0.0.0-20211005172633-e235017c1baf // indirect - github.com/ProtonMail/go-crypto v0.0.0-20220824120805-4b6e5c587895 // indirect - github.com/ProtonMail/go-mime v0.0.0-20220429130430-2192574d760f // indirect + github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8 // indirect + github.com/ProtonMail/go-mime v0.0.0-20221031134845-8fd9bc37cf08 // indirect github.com/ProtonMail/go-srp v0.0.5 // indirect github.com/abiosoft/readline v0.0.0-20180607040430-155bce2042db // indirect github.com/agext/levenshtein v1.2.3 // indirect github.com/andybalholm/cascadia v1.3.1 // indirect github.com/antlr/antlr4/runtime/Go/antlr v1.4.10 // indirect github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect + github.com/bytedance/sonic v1.8.1 // indirect + github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect github.com/chzyer/test v1.0.0 // indirect - github.com/cloudflare/circl v1.2.0 // indirect + github.com/cloudflare/circl v1.3.2 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect github.com/cronokirby/saferith v0.33.0 // indirect github.com/cucumber/gherkin-go/v19 v19.0.3 // indirect @@ -73,48 +76,53 @@ require ( github.com/elastic/go-windows v1.0.1 // indirect github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594 // indirect github.com/emersion/go-vcard v0.0.0-20220507122617-d4056df0ec4a // indirect + github.com/felixge/fgprof v0.9.3 // indirect github.com/flynn-archive/go-shlex v0.0.0-20150515145356-3f9db97f8568 // indirect github.com/gin-contrib/sse v0.1.0 // indirect - github.com/gin-gonic/gin v1.8.1 // indirect + github.com/gin-gonic/gin v1.9.0 // indirect github.com/go-openapi/inflect v0.19.0 // indirect - github.com/go-playground/locales v0.14.0 // indirect - github.com/go-playground/universal-translator v0.18.0 // indirect - github.com/go-playground/validator/v10 v10.11.1 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.11.2 // indirect github.com/gofrs/uuid v4.3.0+incompatible // indirect github.com/golang/protobuf v1.5.2 // indirect + github.com/google/pprof v0.0.0-20211214055906-6f57359322fd // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-immutable-radix v1.3.1 // indirect github.com/hashicorp/go-memdb v1.3.3 // indirect github.com/hashicorp/golang-lru v0.5.4 // indirect - github.com/hashicorp/hcl/v2 v2.14.0 // indirect + github.com/hashicorp/hcl/v2 v2.16.1 // indirect github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901 // indirect github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/cpuid/v2 v2.2.4 // indirect github.com/leodido/go-urn v1.2.1 // indirect github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.16 // indirect + github.com/mattn/go-isatty v0.0.17 // indirect github.com/mattn/go-runewidth v0.0.14 // indirect - github.com/mattn/go-sqlite3 v1.14.15 // indirect + github.com/mattn/go-sqlite3 v1.14.16 // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect - github.com/pelletier/go-toml/v2 v2.0.5 // indirect + github.com/pelletier/go-toml/v2 v2.0.6 // indirect + github.com/pierrec/lz4/v4 v4.1.17 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/procfs v0.8.0 // indirect github.com/rivo/uniseg v0.4.2 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect - github.com/ugorji/go/codec v1.2.7 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.2.10 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect - github.com/zclconf/go-cty v1.11.0 // indirect - golang.org/x/crypto v0.1.0 // indirect - golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect - golang.org/x/sync v0.0.0-20220907140024-f12130a52804 // indirect - golang.org/x/tools v0.1.13-0.20220804200503-81c7dc4e4efa // indirect - google.golang.org/genproto v0.0.0-20220921223823-23cae91e6737 // indirect - gopkg.in/yaml.v2 v2.4.0 // indirect + github.com/zclconf/go-cty v1.12.1 // indirect + golang.org/x/arch v0.2.0 // indirect + golang.org/x/crypto v0.6.0 // indirect + golang.org/x/mod v0.8.0 // indirect + golang.org/x/sync v0.1.0 // indirect + golang.org/x/tools v0.3.1-0.20221202221704-aa9f4b2f3d57 // indirect + google.golang.org/genproto v0.0.0-20230221151758-ace64dc21148 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 406671d4..951e343d 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -ariga.io/atlas v0.7.0 h1:daEFdUsyNm7EHyzcMfjWwq/fVv48fCfad+dIGyobY1k= -ariga.io/atlas v0.7.0/go.mod h1:ft47uSh5hWGDCmQC9DsztZg6Xk+KagM5Ts/mZYKb9JE= +ariga.io/atlas v0.9.1-0.20230119145809-92243f7c55cb h1:mbsFtavDqGdYwdDpP50LGOOZ2hgyGoJcZeOpbgKMyu4= +ariga.io/atlas v0.9.1-0.20230119145809-92243f7c55cb/go.mod h1:T230JFcENj4ZZzMkZrXFDSkv+2kXkUgpJ5FQQ5hMcKU= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= @@ -13,44 +13,43 @@ cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqCl cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -entgo.io/ent v0.11.2 h1:UM2/BUhF2FfsxPHRxLjQbhqJNaDdVlOwNIAMLs2jyto= -entgo.io/ent v0.11.2/go.mod h1:YGHEQnmmIUgtD5b1ICD5vg74dS3npkNnmC5K+0J+IHU= +entgo.io/ent v0.11.8 h1:M/M0QL1CYCUSdqGRXUrXhFYSDRJPsOOrr+RLEej/gyQ= +entgo.io/ent v0.11.8/go.mod h1:ericBi6Q8l3wBH1wEIDfKxw7rcQEuRPyBfbIzjtxJ18= github.com/0xAX/notificator v0.0.0-20220220101646-ee9b8921e557 h1:l6surSnJ3RP4qA1qmKJ+hQn3UjytosdoG27WGjrDlVs= github.com/0xAX/notificator v0.0.0-20220220101646-ee9b8921e557/go.mod h1:sTrmvD/TxuypdOERsDOS7SndZg0rzzcCi1b6wQMXUYM= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= -github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= -github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= +github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g= +github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/ProtonMail/bcrypt v0.0.0-20210511135022-227b4adcab57/go.mod h1:HecWFHognK8GfRDGnFQbW/LiV7A3MX3gZVs45vk5h8I= github.com/ProtonMail/bcrypt v0.0.0-20211005172633-e235017c1baf h1:yc9daCCYUefEs69zUkSzubzjBbL+cmOXgnmt9Fyd9ug= github.com/ProtonMail/bcrypt v0.0.0-20211005172633-e235017c1baf/go.mod h1:o0ESU9p83twszAU8LBeJKFAAMX14tISa0yk4Oo5TOqo= github.com/ProtonMail/docker-credential-helpers v1.1.0 h1:+kvUIpwWcbtP3WFv5sSvkFn/XLzSqPOB5AAthuk9xPk= github.com/ProtonMail/docker-credential-helpers v1.1.0/go.mod h1:mK0aBveCxhnQ756AmaTfXMZDeULvheYVhF/MWMErN5g= -github.com/ProtonMail/gluon v0.14.2-0.20230309105237-65db9f3ab739 h1:lnBpIbJj3o1A24M9vTH97MDgSgN2755fCowtijSYo0U= -github.com/ProtonMail/gluon v0.14.2-0.20230309105237-65db9f3ab739/go.mod h1:z2AxLIiBCT1K+0OBHyaDI7AEaO5qI6/BEC2TE42vs4Q= +github.com/ProtonMail/gluon v0.15.1-0.20230310125443-f755e8ce082a h1:o7gQKDCJMYju8svD4ufB/YfcUcWUPfQjau3MDNF2dQQ= +github.com/ProtonMail/gluon v0.15.1-0.20230310125443-f755e8ce082a/go.mod h1:yA4hk6CJw0BMo+YL8Y3ckCYs5L20sysu9xseshwY3QI= github.com/ProtonMail/go-autostart v0.0.0-20210130080809-00ed301c8e9a h1:D+aZah+k14Gn6kmL7eKxoo/4Dr/lK3ChBcwce2+SQP4= github.com/ProtonMail/go-autostart v0.0.0-20210130080809-00ed301c8e9a/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-20220822140716-1678d6eb0cbe/go.mod h1:UBYPn8k0D56RtnR8RFQMjmh4KrZzWJ5o7Z9SYjossQ8= -github.com/ProtonMail/go-crypto v0.0.0-20220824120805-4b6e5c587895 h1:NsReiLpErIPzRrnogAXYwSoU7txA977LjDGrbkewJbg= -github.com/ProtonMail/go-crypto v0.0.0-20220824120805-4b6e5c587895/go.mod h1:UBYPn8k0D56RtnR8RFQMjmh4KrZzWJ5o7Z9SYjossQ8= +github.com/ProtonMail/go-crypto v0.0.0-20230124153114-0acdc8ae009b/go.mod h1:I0gYDMZ6Z5GRU7l58bNFSkPTFN6Yl12dsUlAZ8xy98g= +github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8 h1:wPbRQzjjwFc0ih8puEVAOFGELsn1zoIIYdxvML7mDxA= +github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8/go.mod h1:I0gYDMZ6Z5GRU7l58bNFSkPTFN6Yl12dsUlAZ8xy98g= github.com/ProtonMail/go-message v0.0.0-20210611055058-fabeff2ec753 h1:I8IsYA297x0QLU80G5I6aLYUu3JYNSpo8j5fkXtFDW0= github.com/ProtonMail/go-message v0.0.0-20210611055058-fabeff2ec753/go.mod h1:NBAn21zgCJ/52WLDyed18YvYFm5tEoeDauubFqLokM4= -github.com/ProtonMail/go-mime v0.0.0-20220302105931-303f85f7fe0f/go.mod h1:NYt+V3/4rEeDuaev/zw1zCq8uqVEuPHzDPo3OZrlGJ4= -github.com/ProtonMail/go-mime v0.0.0-20220429130430-2192574d760f h1:4IWzKjHzZxdrW9k4zl/qCwenOVHDbVDADPPHFLjs0Oc= -github.com/ProtonMail/go-mime v0.0.0-20220429130430-2192574d760f/go.mod h1:qRZgbeASl2a9OwmsV85aWwRqic0NHPh+9ewGAzb4cgM= -github.com/ProtonMail/go-proton-api v0.3.1-0.20230308164916-42e487b4ad74 h1:gtzxYZcF7rGdsYi0g2+PcLYBCa9H1+CrVKY+5N/yYGE= -github.com/ProtonMail/go-proton-api v0.3.1-0.20230308164916-42e487b4ad74/go.mod h1:JUo5IQG0hNuPRuDpOUsCOvtee6UjTEHHF1QN2i8RSos= +github.com/ProtonMail/go-mime v0.0.0-20221031134845-8fd9bc37cf08 h1:dS7r5z4iGS0qCjM7UwWdsEMzQesUQbGcXdSm2/tWboA= +github.com/ProtonMail/go-mime v0.0.0-20221031134845-8fd9bc37cf08/go.mod h1:qRZgbeASl2a9OwmsV85aWwRqic0NHPh+9ewGAzb4cgM= +github.com/ProtonMail/go-proton-api v0.4.1-0.20230313102028-4da9318e5f77 h1:/BZaVBC2s3Q/0Z++vZ5hJsijOwy2HgK2cVVwxRCjLQs= +github.com/ProtonMail/go-proton-api v0.4.1-0.20230313102028-4da9318e5f77/go.mod h1:4AXhqhB+AGVasVIlift9Lr1Btxg5S83xXPiyiT7mKUc= github.com/ProtonMail/go-rfc5322 v0.11.0 h1:o5Obrm4DpmQEffvgsVqG6S4BKwC1Wat+hYwjIp2YcCY= github.com/ProtonMail/go-rfc5322 v0.11.0/go.mod h1:6oOKr0jXvpoE6pwTx/HukigQpX2J9WUf6h0auplrFTw= github.com/ProtonMail/go-srp v0.0.5 h1:xhUioxZgDbCnpo9JehyFhwwsn9JLWkUGfB0oiKXgiGg= github.com/ProtonMail/go-srp v0.0.5/go.mod h1:06iYHtLXW8vjLtccWj++x3MKy65sIT8yZd7nrJF49rs= -github.com/ProtonMail/gopenpgp/v2 v2.4.10 h1:EYgkxzwmQvsa6kxxkgP1AwzkFqKHscF2UINxaSn6rdI= -github.com/ProtonMail/gopenpgp/v2 v2.4.10/go.mod h1:CTRA7/toc/4DxDy5Du4hPDnIZnJvXSeQ8LsRTOUJoyc= -github.com/PuerkitoBio/goquery v1.8.0 h1:PJTF7AmFCFKk1N6V6jmKfrNH9tV5pNE6lZMkG0gta/U= -github.com/PuerkitoBio/goquery v1.8.0/go.mod h1:ypIiRMtY7COPGk+I/YbZLbxsxn9g5ejnI2HSMtkjZvI= +github.com/ProtonMail/gopenpgp/v2 v2.5.2 h1:97SjlWNAxXl9P22lgwgrZRshQdiEfAht0g3ZoiA1GCw= +github.com/ProtonMail/gopenpgp/v2 v2.5.2/go.mod h1:52qDaCnto6r+CoWbuU50T77XQt99lIs46HtHtvgFO3o= +github.com/PuerkitoBio/goquery v1.8.1 h1:uQxhNlArOIdbrH1tr0UXwdVFgDcZDrZVdcpygAcwmWM= +github.com/PuerkitoBio/goquery v1.8.1/go.mod h1:Q8ICL1kNUJ2sXGoAhPGUdYDJvgQgHzJsnnd3H7Ho5jQ= github.com/abiosoft/ishell v2.0.0+incompatible h1:zpwIuEHc37EzrsIYah3cpevrIc8Oma7oZPxr03tlmmw= github.com/abiosoft/ishell v2.0.0+incompatible/go.mod h1:HQR9AqF2R3P4XXpMpI0NAzgHf/aS6+zVXRj14cVk9qg= github.com/abiosoft/readline v0.0.0-20180607040430-155bce2042db h1:CjPUSXOiYptLbTdr1RceuZgSFDQ7U15ITERUGrUORx8= @@ -75,19 +74,27 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24 github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= -github.com/bradenaw/juniper v0.8.0 h1:sdanLNdJbLjcLj993VYIwUHlUVkLzvgiD/x9O7cvvxk= -github.com/bradenaw/juniper v0.8.0/go.mod h1:Z2B7aJlQ7xbfWsnMLROj5t/5FQ94/MkIdKC30J4WvzI= +github.com/bradenaw/juniper v0.10.2 h1:EY7r8SJJrigJ7lvWk6ews3K5RD4XTG9z+WSwHJKijP4= +github.com/bradenaw/juniper v0.10.2/go.mod h1:Z2B7aJlQ7xbfWsnMLROj5t/5FQ94/MkIdKC30J4WvzI= github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= -github.com/bwesterb/go-ristretto v1.2.1/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= +github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= +github.com/bytedance/sonic v1.8.1 h1:NqAHCaGaTzro0xMmnTCLUyRlbEP6r8MCA1cJUrH3Pu4= +github.com/bytedance/sonic v1.8.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= +github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= +github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/logex v1.2.1 h1:XHDu3E6q+gdHgsdTPH6ImJMIp436vR6MPtH8gP05QzM= github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04= github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I= -github.com/cloudflare/circl v1.2.0 h1:NheeISPSUcYftKlfrLuOo4T62FkmD4t4jviLfFFYaec= -github.com/cloudflare/circl v1.2.0/go.mod h1:Ch2UgYr6ti2KTtlejELlROl0YIYj7SLjAC8M+INXlMk= +github.com/cloudflare/circl v1.3.2 h1:VWp8dY3yH69fdM7lM6A1+NhhVoDu9vqK0jOgmkQHFWk= +github.com/cloudflare/circl v1.3.2/go.mod h1:+CauBF6R70Jqcyl8N2hC8pAXYbWkGIezuSbuGLtRhnw= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= @@ -96,7 +103,6 @@ github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfc github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/cronokirby/saferith v0.33.0 h1:TgoQlfsD4LIwx71+ChfRcIpjkw+RPOapDEVxa+LhwLo= github.com/cronokirby/saferith v0.33.0/go.mod h1:QKJhjoqUtBsXCAVEjw38mFqoi7DebT7kthcD7UzbnoA= github.com/cucumber/gherkin-go/v19 v19.0.3 h1:mMSKu1077ffLbTJULUfM5HPokgeBcIGboyeNUof1MdE= @@ -120,8 +126,8 @@ github.com/elastic/go-sysinfo v1.8.1 h1:4Yhj+HdV6WjbCRgGdZpPJ8lZQlXZLKDAeIkmQ/VR github.com/elastic/go-sysinfo v1.8.1/go.mod h1:JfllUnzoQV/JRYymbH3dO1yggI3mV2oTKSXsDHM+uIM= github.com/elastic/go-windows v1.0.1 h1:AlYZOldA+UJ0/2nBuqWdo90GFCgG9xuyw9SYzGUtJm0= github.com/elastic/go-windows v1.0.1/go.mod h1:FoVvqWSun28vaDQPbj2Elfc0JahhPB7WQEGa3c814Ss= -github.com/emersion/go-imap v1.2.1-0.20220429085312-746087b7a317 h1:i0cBrdFLm8A/3hWEjn/BwdXLBplFJoZtu63p7bjrmaI= -github.com/emersion/go-imap v1.2.1-0.20220429085312-746087b7a317/go.mod h1:Qlx1FSx2FTxjnjWpIlVNEuX+ylerZQNFE5NsmKFSejY= +github.com/emersion/go-imap v1.2.1 h1:+s9ZjMEjOB8NzZMVTM3cCenz2JrQIGGo5j1df19WjTA= +github.com/emersion/go-imap v1.2.1/go.mod h1:Qlx1FSx2FTxjnjWpIlVNEuX+ylerZQNFE5NsmKFSejY= github.com/emersion/go-imap-id v0.0.0-20190926060100-f94a56b9ecde h1:43mBoVwooyLm1+1YVf5nvn1pSFWhw7rOpcrp1Jg/qk0= github.com/emersion/go-imap-id v0.0.0-20190926060100-f94a56b9ecde/go.mod h1:sPwp0FFboaK/bxsrUz1lNrDMUCsZUsKC5YuM4uRVRVs= github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ= @@ -136,6 +142,8 @@ github.com/emersion/go-vcard v0.0.0-20220507122617-d4056df0ec4a/go.mod h1:HMJKR5 github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/felixge/fgprof v0.9.3 h1:VvyZxILNuCiUCSXtPtYmmtGvb65nqXh2QFWc0Wpf2/g= +github.com/felixge/fgprof v0.9.3/go.mod h1:RdbpDgzqYVh/T9fPELJyV7EYJuHB55UTEULNun8eiPw= github.com/flynn-archive/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BMXYYRWTLOJKlh+lOBt6nUQgXAfB7oVIQt5cNreqSLI= 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= @@ -144,8 +152,8 @@ github.com/getsentry/sentry-go v0.15.0/go.mod h1:RZPJKSw+adu8PBNygiri/A98FqVr2Ht github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= -github.com/gin-gonic/gin v1.8.1 h1:4+fr/el88TOO3ewCmQr8cx/CtZ/umlIRIs5M4NTNjf8= -github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk= +github.com/gin-gonic/gin v1.9.0 h1:OjyFBKICoexlu99ctXNR2gg+c5pKrKMuyjgARg9qeY8= +github.com/gin-gonic/gin v1.9.0/go.mod h1:W1Me9+hsUSyj3CePGrd1/QrKJMSJ1Tu/0hFEH89961k= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= @@ -153,20 +161,19 @@ github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9 github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-openapi/inflect v0.19.0 h1:9jCH9scKIbHeV9m12SmPilScz6krDxKRasNNSNPXu/4= github.com/go-openapi/inflect v0.19.0/go.mod h1:lHpZVlpIQqLyKwJ4N+YSc9hchQy/i12fJykb83CRBH4= -github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= -github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= -github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU= -github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= -github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho= -github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= -github.com/go-playground/validator/v10 v10.11.1 h1:prmOlTVv+YjZjmRmNSF3VmspqJIxJWXmqUsHwfTRRkQ= -github.com/go-playground/validator/v10 v10.11.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.11.2 h1:q3SHpufmypg+erIExEKUmsgmhDTyhcJ38oeKGACXohU= +github.com/go-playground/validator/v10 v10.11.2/go.mod h1:NieE624vt4SCTJtD87arVLvdmjPAeV8BQlHtMnw9D7s= github.com/go-resty/resty/v2 v2.7.0 h1:me+K9p3uhSmXtrBZ4k9jcEAfJmuC8IivWHwaLZwPrFY= github.com/go-resty/resty/v2 v2.7.0/go.mod h1:9PWDzw47qPphMRFfhsyk0NnSgvluHcljSMVIq3w7q0I= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68= -github.com/goccy/go-json v0.9.11 h1:/pAaQDLHEoCq/5FFmSKBswWmK6H0e8g4159Kc/X/nqk= -github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/goccy/go-json v0.10.0 h1:mXKd9Qw4NuzShiRlOXKews24ufknHO7gx30lsDyokKA= +github.com/goccy/go-json v0.10.0/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/godbus/dbus v4.1.0+incompatible h1:WqqLRTsQic3apZUK9qC5sGNfXthmPXzUZ7nQPrNITa4= github.com/godbus/dbus v4.1.0+incompatible/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= @@ -198,6 +205,8 @@ github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/ github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20211214055906-6f57359322fd h1:1FjCyPC+syAzJ5/2S8fqdZK1R22vvA0J7JZKcuOIQ7Y= +github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -239,12 +248,13 @@ github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hashicorp/hcl/v2 v2.14.0 h1:jX6+Q38Ly9zaAJlAjnFVyeNSNCKKW8D0wvyg7vij5Wc= -github.com/hashicorp/hcl/v2 v2.14.0/go.mod h1:e4z5nxYlWNPdDSNYX+ph14EvWYMFm3eP0zIUqPc2jr0= +github.com/hashicorp/hcl/v2 v2.16.1 h1:BwuxEMD/tsYgbhIW7UuI3crjovf3MzuFWiVgiv57iHg= +github.com/hashicorp/hcl/v2 v2.16.1/go.mod h1:JRmR89jycNkrrqnMmvPDMd56n1rQJ2Q6KocSLCMCXng= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/ianlancetaylor/demangle v0.0.0-20210905161508-09a460cdf81d/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jaytaylor/html2text v0.0.0-20211105163654-bc68cce691ba h1:QFQpJdgbON7I0jr2hYW7Bs+XV0qjc3d5tZoDnRFnqTg= github.com/jaytaylor/html2text v0.0.0-20211105163654-bc68cce691ba/go.mod h1:CVKlgaMiht+LXvHG173ujK6JUhZXKb2u/BQtjPDIvyk= @@ -261,16 +271,17 @@ github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfV github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk= +github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= -github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 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/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348 h1:MtvEpTB6LX3vkb4ax0b5D2DHbNAUsen0Gx5wZoq3lV4= github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= @@ -282,13 +293,14 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= -github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= +github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI= -github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= +github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y= +github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA= @@ -314,17 +326,20 @@ github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2DcNVpwGmV9E1BkGknEliJkfwQj0= +github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58/go.mod h1:DXv8WO4yhMYhSNPKjeNKa5WY9YCIEBRbNzFFPJbWO6Y= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= -github.com/pelletier/go-toml/v2 v2.0.5 h1:ipoSadvV8oGUjnUbMub59IDPPwfxF694nG/jwbMiyQg= -github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas= +github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU= +github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek= +github.com/pierrec/lz4/v4 v4.1.17 h1:kV4Ip+/hUBC+8T6+2EgburRtkE9ef4nbY3f4dFhGjMc= +github.com/pierrec/lz4/v4 v4.1.17/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= -github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/profile v1.6.0 h1:hUDfIISABYI59DyeB3OTay/HxSRwTQ8rB/H83k6r5dM= -github.com/pkg/profile v1.6.0/go.mod h1:qBsxPvzyUincmltOk6iyRVxHYg4adc0OFOv72ZdLa18= +github.com/pkg/profile v1.7.0 h1:hnbDkaNWPCLMO9wGLdBFTIZvzDrDfBM2072E1S9gJkA= +github.com/pkg/profile v1.7.0/go.mod h1:8Uer0jas47ZQMJ7VD+OHknK4YDY07LPUC6dEvqDjvNo= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= @@ -344,9 +359,7 @@ github.com/rivo/uniseg v0.4.2 h1:YwD0ulJSJytLpiaWua0sBDusfsCZohxjxzVTYjwxfV8= github.com/rivo/uniseg v0.4.2/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= -github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= @@ -355,7 +368,6 @@ github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= @@ -376,24 +388,26 @@ github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf h1:pvbZ0lM0XWPBqUKqFU8cma github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf/go.mod h1:RJID2RhlZKId02nZ62WenDCkgHFerpIOmW0iT7GKmXM= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0 h1:M2gUjqZET1qApGOWNSnZ49BAIMX4F/1plDv3+l31EJ4= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= -github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= -github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0= -github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= -github.com/urfave/cli/v2 v2.20.3 h1:lOgGidH/N5loaigd9HjFsOIhXSTrzl7tBpHswZ428w4= -github.com/urfave/cli/v2 v2.20.3/go.mod h1:1CNUng3PtjQMtRzJO4FMXBQvkGtuYRxxiR9xMa7jMwI= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go/codec v1.2.10 h1:eimT6Lsr+2lzmSZxPhLFoOWFmQqwk0fllJJ5hEbTXtQ= +github.com/ugorji/go/codec v1.2.10/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +github.com/urfave/cli/v2 v2.24.4 h1:0gyJJEBYtCV87zI/x2nZCPyDxD51K6xM8SkwjHFCNEU= +github.com/urfave/cli/v2 v2.24.4/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc= github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU= github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= @@ -402,16 +416,20 @@ github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/zclconf/go-cty v1.11.0 h1:726SxLdi2SDnjY+BStqB9J1hNp4+2WlzyXLuimibIe0= -github.com/zclconf/go-cty v1.11.0/go.mod h1:s9IfD1LK5ccNMSWCVFCE2rJfHiZgi7JijgeWIMfhLvA= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/zclconf/go-cty v1.12.1 h1:PcupnljUm9EIvbgSHQnHhUr3fO6oFmkOrvs2BAFNXXY= +github.com/zclconf/go-cty v1.12.1/go.mod h1:s9IfD1LK5ccNMSWCVFCE2rJfHiZgi7JijgeWIMfhLvA= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= -go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo= +go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= +go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/arch v0.2.0 h1:W1sUEHXiJTfjaFJ5SLo0N6lZn+0eO5gWD1MFeTGqQEY= +golang.org/x/arch v0.2.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -420,18 +438,16 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU= -golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= +golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc= +golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= -golang.org/x/exp v0.0.0-20221023144134-a1e5550cf13e h1:SkwG94eNiiYJhbeDE018Grw09HIN/KB9NlRmZsrzfWs= -golang.org/x/exp v0.0.0-20221023144134-a1e5550cf13e/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= +golang.org/x/exp v0.0.0-20230213192124-5e25df0256eb h1:PaBZQdo+iSDyHT053FjUCgZQ/9uqVwPOcl7KSWhKn6w= +golang.org/x/exp v0.0.0-20230213192124-5e25df0256eb/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -440,18 +456,16 @@ golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTk golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mobile v0.0.0-20200801112145-973feb4309de/go.mod h1:skQtrUTUwhdJvXM/2KKJzY8pDgNr9I/FOMqDVRPBUS4= +golang.org/x/mobile v0.0.0-20221110043201-43a038452099/go.mod h1:aAjjkJNdrh3PMckS4B10TGS2nag27cbKR1y2BpUxsiY= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.1.1-0.20191209134235-331c550502dd/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -471,9 +485,9 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.1.0 h1:hZ/3BUoy5aId7sCpA/Tc5lt8DkFgdVS2onTpJsZ/fl0= -golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -483,8 +497,10 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220907140024-f12130a52804 h1:0SH2R3f1b1VmIMG7BXbEZCBUu2dKmHschSmjqGUrW8A= -golang.org/x/sync v0.0.0-20220907140024-f12130a52804/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -494,7 +510,6 @@ golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -508,15 +523,18 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210819135213-f52c844e1c1c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220315194320-039c03cc5b86/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= -golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= @@ -524,8 +542,8 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5-0.20201125200606-c27b9fd57aec/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg= -golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -546,11 +564,11 @@ golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.13-0.20220804200503-81c7dc4e4efa h1:uKcci2q7Qtp6nMTC/AAvfNUAldFtJuHWV9/5QWiypts= -golang.org/x/tools v0.1.13-0.20220804200503-81c7dc4e4efa/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.3.1-0.20221202221704-aa9f4b2f3d57 h1:/X0t/E4VxbZE7MLS7auvE7YICHeVvbIa9vkOVvYW/24= +golang.org/x/tools v0.3.1-0.20221202221704-aa9f4b2f3d57/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -573,13 +591,13 @@ google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98 google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20220921223823-23cae91e6737 h1:K1zaaMdYBXRyX+cwFnxj7M6zwDyumLQMZ5xqwGvjreQ= -google.golang.org/genproto v0.0.0-20220921223823-23cae91e6737/go.mod h1:2r/26NEF3bFmT3eC3aZreahSal0C3Shl8Gi6vyDYqOQ= +google.golang.org/genproto v0.0.0-20230221151758-ace64dc21148 h1:muK+gVBJBfFb4SejshDBlN2/UgxCCOKH9Y34ljqEGOc= +google.golang.org/genproto v0.0.0-20230221151758-ace64dc21148/go.mod h1:3Dl5ZL0q0isWJt+FVcfpQyirqemEuLAK/iFvg1UP1Hw= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -google.golang.org/grpc v1.50.1 h1:DS/BukOZWp8s6p4Dt/tOaJaTQyPyOoCcrjroHuCeLzY= -google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= +google.golang.org/grpc v1.53.0 h1:LAv2ds7cmFV/XTS3XG1NneeENYrXGmorPxsBbptIjNc= +google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= @@ -598,11 +616,8 @@ gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= @@ -612,3 +627,4 @@ honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM= howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/internal/app/app.go b/internal/app/app.go index 6e5c6a62..36240e74 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -81,7 +81,7 @@ const ( appUsage = "Proton Mail IMAP and SMTP Bridge" ) -func New() *cli.App { //nolint:funlen +func New() *cli.App { app := cli.NewApp() app.Name = constants.FullAppName @@ -156,7 +156,7 @@ func New() *cli.App { //nolint:funlen return app } -func run(c *cli.Context) error { //nolint:funlen +func run(c *cli.Context) error { // Seed the default RNG from the math/rand package. rand.Seed(time.Now().UnixNano()) @@ -208,9 +208,14 @@ func run(c *cli.Context) error { //nolint:funlen } // Ensure we are the only instance running. - return withSingleInstance(locations, version, func() error { + settings, err := locations.ProvideSettingsPath() + if err != nil { + logrus.WithError(err).Error("Failed to get settings path") + } + + return withSingleInstance(settings, locations.GetLockFile(), version, func() error { // Unlock the encrypted vault. - return WithVault(locations, func(vault *vault.Vault, insecure, corrupt bool) error { + return WithVault(locations, func(v *vault.Vault, insecure, corrupt bool) error { // Report insecure vault. if insecure { _ = reporter.ReportMessageWithContext("Vault is insecure", map[string]interface{}{}) @@ -221,27 +226,27 @@ func run(c *cli.Context) error { //nolint:funlen _ = reporter.ReportMessageWithContext("Vault is corrupt", map[string]interface{}{}) } - if !vault.Migrated() { + if !v.Migrated() { // Migrate old settings into the vault. - if err := migrateOldSettings(vault); err != nil { + if err := migrateOldSettings(v); err != nil { logrus.WithError(err).Error("Failed to migrate old settings") } // Migrate old accounts into the vault. - if err := migrateOldAccounts(locations, vault); err != nil { + if err := migrateOldAccounts(locations, v); err != nil { logrus.WithError(err).Error("Failed to migrate old accounts") } // The vault has been migrated. - if err := vault.SetMigrated(); err != nil { + if err := v.SetMigrated(); err != nil { logrus.WithError(err).Error("Failed to mark vault as migrated") } } // Load the cookies from the vault. - return withCookieJar(vault, func(cookieJar http.CookieJar) error { + return withCookieJar(v, func(cookieJar http.CookieJar) error { // Create a new bridge instance. - return withBridge(c, exe, locations, version, identifier, crashHandler, reporter, vault, cookieJar, func(b *bridge.Bridge, eventCh <-chan events.Event) error { + return withBridge(c, exe, locations, version, identifier, crashHandler, reporter, v, cookieJar, func(b *bridge.Bridge, eventCh <-chan events.Event) error { if insecure { logrus.Warn("The vault key could not be retrieved; the vault will not be encrypted") b.PushError(bridge.ErrVaultInsecure) @@ -266,15 +271,15 @@ func run(c *cli.Context) error { //nolint:funlen } // If there's another instance already running, try to raise it and exit. -func withSingleInstance(locations *locations.Locations, version *semver.Version, fn func() error) error { +func withSingleInstance(settingPath, lockFile string, version *semver.Version, fn func() error) error { logrus.Debug("Checking for other instances") defer logrus.Debug("Single instance stopped") - lock, err := checkSingleInstance(locations.GetLockFile(), version) + lock, err := checkSingleInstance(settingPath, lockFile, version) if err != nil { logrus.Info("Another instance is already running; raising it") - if ok := focus.TryRaise(); !ok { + if ok := focus.TryRaise(settingPath); !ok { return fmt.Errorf("another instance is already running but it could not be raised") } diff --git a/internal/app/bridge.go b/internal/app/bridge.go index 668aca59..6705227a 100644 --- a/internal/app/bridge.go +++ b/internal/app/bridge.go @@ -23,6 +23,7 @@ import ( "runtime" "github.com/Masterminds/semver/v3" + "github.com/ProtonMail/gluon/imap" "github.com/ProtonMail/go-autostart" "github.com/ProtonMail/gopenpgp/v2/crypto" "github.com/ProtonMail/proton-bridge/v3/internal/bridge" @@ -40,13 +41,11 @@ import ( "github.com/urfave/cli/v2" ) -const vaultSecretName = "bridge-vault-key" - // deleteOldGoIMAPFiles Set with `-ldflags -X app.deleteOldGoIMAPFiles=true` to enable cleanup of old imap cache data. var deleteOldGoIMAPFiles bool //nolint:gochecknoglobals // withBridge creates creates and tears down the bridge. -func withBridge( //nolint:funlen +func withBridge( c *cli.Context, exe string, locations *locations.Locations, @@ -110,6 +109,7 @@ func withBridge( //nolint:funlen // Crash and report stuff crashHandler, reporter, + imap.DefaultEpochUIDValidityGenerator(), // The logging stuff. c.String(flagLogIMAP) == "client" || c.String(flagLogIMAP) == "all", diff --git a/internal/app/migration.go b/internal/app/migration.go index 1124de9b..4c440e6b 100644 --- a/internal/app/migration.go +++ b/internal/app/migration.go @@ -187,7 +187,6 @@ func migrateOldAccount(userID string, store *credentials.Store, v *vault.Vault) return nil } -// nolint:funlen func migratePrefsToVault(vault *vault.Vault, b []byte) error { var prefs struct { IMAPPort int `json:"user_port_imap,,string"` @@ -265,14 +264,6 @@ func migratePrefsToVault(vault *vault.Vault, b []byte) error { errs = multierror.Append(errs, fmt.Errorf("failed to migrate show all mail: %w", err)) } - if err := vault.SetSyncWorkers(prefs.FetchWorkers); err != nil { - errs = multierror.Append(errs, fmt.Errorf("failed to migrate sync workers: %w", err)) - } - - if err := vault.SetSyncAttPool(prefs.AttachmentWorkers); err != nil { - errs = multierror.Append(errs, fmt.Errorf("failed to migrate sync attachment pool: %w", err)) - } - if err := vault.SetCookies([]byte(prefs.Cookies)); err != nil { errs = multierror.Append(errs, fmt.Errorf("failed to migrate cookies: %w", err)) } diff --git a/internal/app/migration_test.go b/internal/app/migration_test.go index a9e128e9..fd0b41e1 100644 --- a/internal/app/migration_test.go +++ b/internal/app/migration_test.go @@ -68,8 +68,6 @@ func TestMigratePrefsToVault(t *testing.T) { require.True(t, vault.GetAutostart()) // Check that the other app settings have been migrated. - require.Equal(t, 16, vault.SyncWorkers()) - require.Equal(t, 16, vault.SyncAttPool()) require.False(t, vault.GetProxyAllowed()) require.False(t, vault.GetShowAllMail()) diff --git a/internal/app/singleinstance.go b/internal/app/singleinstance.go index da9ce775..4e6b6dc7 100644 --- a/internal/app/singleinstance.go +++ b/internal/app/singleinstance.go @@ -34,7 +34,7 @@ import ( // // For macOS and Linux when already running version is older than this instance // it will kill old and continue with this new bridge (i.e. no error returned). -func checkSingleInstance(lockFilePath string, curVersion *semver.Version) (*os.File, error) { +func checkSingleInstance(settingPath, lockFilePath string, curVersion *semver.Version) (*os.File, error) { if lock, err := singleinstance.CreateLockFile(lockFilePath); err == nil { logrus.WithField("path", lockFilePath).Debug("Created lock file; no other instance is running") return lock, nil @@ -44,7 +44,7 @@ func checkSingleInstance(lockFilePath string, curVersion *semver.Version) (*os.F // We couldn't create the lock file, so another instance is probably running. // Check if it's an older version of the app. - lastVersion, ok := focus.TryVersion() + lastVersion, ok := focus.TryVersion(settingPath) if !ok { return nil, fmt.Errorf("failed to determine version of running instance") } diff --git a/internal/app/vault.go b/internal/app/vault.go index d49be879..55b583b1 100644 --- a/internal/app/vault.go +++ b/internal/app/vault.go @@ -18,18 +18,15 @@ package app import ( - "encoding/base64" "fmt" "path" - "github.com/ProtonMail/gopenpgp/v2/crypto" "github.com/ProtonMail/proton-bridge/v3/internal/certs" "github.com/ProtonMail/proton-bridge/v3/internal/constants" "github.com/ProtonMail/proton-bridge/v3/internal/locations" "github.com/ProtonMail/proton-bridge/v3/internal/vault" "github.com/ProtonMail/proton-bridge/v3/pkg/keychain" "github.com/sirupsen/logrus" - "golang.org/x/exp/slices" ) func WithVault(locations *locations.Locations, fn func(*vault.Vault, bool, bool) error) error { @@ -51,7 +48,9 @@ func WithVault(locations *locations.Locations, fn func(*vault.Vault, bool, bool) if installed := encVault.GetCertsInstalled(); !installed { logrus.Debug("Installing certificates") - if err := certs.NewInstaller().InstallCert(encVault.GetBridgeTLSCert()); err != nil { + certPEM, _ := encVault.GetBridgeTLSCert() + + if err := certs.NewInstaller().InstallCert(certPEM); err != nil { return fmt.Errorf("failed to install certs: %w", err) } @@ -80,7 +79,7 @@ func newVault(locations *locations.Locations) (*vault.Vault, bool, bool, error) insecure bool ) - if key, err := getVaultKey(vaultDir); err != nil { + if key, err := loadVaultKey(vaultDir); err != nil { insecure = true // We store the insecure vault in a separate directory @@ -102,42 +101,25 @@ func newVault(locations *locations.Locations) (*vault.Vault, bool, bool, error) return vault, insecure, corrupt, nil } -func getVaultKey(vaultDir string) ([]byte, error) { +func loadVaultKey(vaultDir string) ([]byte, error) { helper, err := vault.GetHelper(vaultDir) if err != nil { return nil, fmt.Errorf("could not get keychain helper: %w", err) } - keychain, err := keychain.NewKeychain(helper, constants.KeyChainName) + kc, err := keychain.NewKeychain(helper, constants.KeyChainName) if err != nil { return nil, fmt.Errorf("could not create keychain: %w", err) } - secrets, err := keychain.List() + has, err := vault.HasVaultKey(kc) if err != nil { - return nil, fmt.Errorf("could not list keychain: %w", err) + return nil, fmt.Errorf("could not check for vault key: %w", err) } - if !slices.Contains(secrets, vaultSecretName) { - tok, err := crypto.RandomToken(32) - if err != nil { - return nil, fmt.Errorf("could not generate random token: %w", err) - } - - if err := keychain.Put(vaultSecretName, base64.StdEncoding.EncodeToString(tok)); err != nil { - return nil, fmt.Errorf("could not put keychain item: %w", err) - } + if has { + return vault.GetVaultKey(kc) } - _, keyEnc, err := keychain.Get(vaultSecretName) - if err != nil { - return nil, fmt.Errorf("could not get keychain item: %w", err) - } - - keyDec, err := base64.StdEncoding.DecodeString(keyEnc) - if err != nil { - return nil, fmt.Errorf("could not decode keychain item: %w", err) - } - - return keyDec, nil + return vault.NewVaultKey(kc) } diff --git a/internal/bridge/api.go b/internal/bridge/api.go index 1f0edaca..cbe2f2be 100644 --- a/internal/bridge/api.go +++ b/internal/bridge/api.go @@ -32,14 +32,12 @@ func defaultAPIOptions( version *semver.Version, cookieJar http.CookieJar, transport http.RoundTripper, - poolSize int, ) []proton.Option { return []proton.Option{ proton.WithHostURL(apiURL), proton.WithAppVersion(constants.AppVersion(version.Original())), proton.WithCookieJar(cookieJar), proton.WithTransport(transport), - proton.WithAttPoolSize(poolSize), proton.WithLogger(logrus.StandardLogger()), } } diff --git a/internal/bridge/api_default.go b/internal/bridge/api_default.go index a71433cc..6f800d77 100644 --- a/internal/bridge/api_default.go +++ b/internal/bridge/api_default.go @@ -32,7 +32,6 @@ func newAPIOptions( version *semver.Version, cookieJar http.CookieJar, transport http.RoundTripper, - poolSize int, ) []proton.Option { - return defaultAPIOptions(apiURL, version, cookieJar, transport, poolSize) + return defaultAPIOptions(apiURL, version, cookieJar, transport) } diff --git a/internal/bridge/api_qa.go b/internal/bridge/api_qa.go index cf182330..618ce42e 100644 --- a/internal/bridge/api_qa.go +++ b/internal/bridge/api_qa.go @@ -33,9 +33,8 @@ func newAPIOptions( version *semver.Version, cookieJar http.CookieJar, transport http.RoundTripper, - poolSize int, ) []proton.Option { - opt := defaultAPIOptions(apiURL, version, cookieJar, transport, poolSize) + opt := defaultAPIOptions(apiURL, version, cookieJar, transport) if host := os.Getenv("BRIDGE_API_HOST"); host != "" { opt = append(opt, proton.WithHostURL(host)) diff --git a/internal/bridge/bridge.go b/internal/bridge/bridge.go index 53a320bb..27cde2a4 100644 --- a/internal/bridge/bridge.go +++ b/internal/bridge/bridge.go @@ -31,6 +31,7 @@ import ( "github.com/Masterminds/semver/v3" "github.com/ProtonMail/gluon" imapEvents "github.com/ProtonMail/gluon/events" + "github.com/ProtonMail/gluon/imap" "github.com/ProtonMail/gluon/reporter" "github.com/ProtonMail/gluon/watcher" "github.com/ProtonMail/go-proton-api" @@ -124,10 +125,12 @@ type Bridge struct { // goUpdate triggers a check/install of updates. goUpdate func() + + uidValidityGenerator imap.UIDValidityGenerator } // New creates a new bridge. -func New( //nolint:funlen +func New( locator Locator, // the locator to provide paths to store data vault *vault.Vault, // the bridge's encrypted data store autostarter Autostarter, // the autostarter to manage autostart settings @@ -142,12 +145,13 @@ func New( //nolint:funlen proxyCtl ProxyController, // the DoH controller crashHandler async.PanicHandler, reporter reporter.Reporter, + uidValidityGenerator imap.UIDValidityGenerator, logIMAPClient, logIMAPServer bool, // whether to log IMAP client/server activity logSMTP bool, // whether to log SMTP activity ) (*Bridge, <-chan events.Event, error) { // api is the user's API manager. - api := proton.New(newAPIOptions(apiURL, curVersion, cookieJar, roundTripper, vault.SyncAttPool())...) + api := proton.New(newAPIOptions(apiURL, curVersion, cookieJar, roundTripper)...) // tasks holds all the bridge's background tasks. tasks := async.NewGroup(context.Background(), crashHandler) @@ -171,6 +175,7 @@ func New( //nolint:funlen api, identifier, proxyCtl, + uidValidityGenerator, logIMAPClient, logIMAPServer, logSMTP, ) if err != nil { @@ -185,22 +190,9 @@ func New( //nolint:funlen return nil, nil, fmt.Errorf("failed to initialize bridge: %w", err) } - // Start serving IMAP. - if err := bridge.serveIMAP(); err != nil { - logrus.WithError(err).Error("IMAP error") - bridge.PushError(ErrServeIMAP) - } - - // Start serving SMTP. - if err := bridge.serveSMTP(); err != nil { - logrus.WithError(err).Error("SMTP error") - bridge.PushError(ErrServeSMTP) - } - return bridge, eventCh, nil } -// nolint:funlen func newBridge( tasks *async.Group, imapEventCh chan imapEvents.Event, @@ -216,6 +208,7 @@ func newBridge( api *proton.Manager, identifier Identifier, proxyCtl ProxyController, + uidValidityGenerator imap.UIDValidityGenerator, logIMAPClient, logIMAPServer, logSMTP bool, ) (*Bridge, error) { @@ -254,12 +247,13 @@ func newBridge( logIMAPServer, imapEventCh, tasks, + uidValidityGenerator, ) if err != nil { return nil, fmt.Errorf("failed to create IMAP server: %w", err) } - focusService, err := focus.NewService(curVersion) + focusService, err := focus.NewService(locator, curVersion) if err != nil { return nil, fmt.Errorf("failed to create focus service: %w", err) } @@ -300,6 +294,8 @@ func newBridge( lastVersion: lastVersion, tasks: tasks, + + uidValidityGenerator: uidValidityGenerator, } bridge.smtpServer = newSMTPServer(bridge, tlsConfig, logSMTP) @@ -307,7 +303,6 @@ func newBridge( return bridge, nil } -// nolint:funlen func (bridge *Bridge) init(tlsReporter TLSReporter) error { // Enable or disable the proxy at startup. if bridge.vault.GetProxyAllowed() { @@ -376,16 +371,32 @@ func (bridge *Bridge) init(tlsReporter TLSReporter) error { }) }) - // Attempt to lazy load users when triggered. + // We need to load users before we can start the IMAP and SMTP servers. + // We must only start the servers once. + var once sync.Once + + // Attempt to load users from the vault when triggered. bridge.goLoad = bridge.tasks.Trigger(func(ctx context.Context) { if err := bridge.loadUsers(ctx); err != nil { logrus.WithError(err).Error("Failed to load users") if netErr := new(proton.NetError); !errors.As(err, &netErr) { sentry.ReportError(bridge.reporter, "Failed to load users", err) } - } else { - bridge.publish(events.AllUsersLoaded{}) + return } + + bridge.publish(events.AllUsersLoaded{}) + + // Once all users have been loaded, start the bridge's IMAP and SMTP servers. + once.Do(func() { + if err := bridge.serveIMAP(); err != nil { + logrus.WithError(err).Error("Failed to start IMAP server") + } + + if err := bridge.serveSMTP(); err != nil { + logrus.WithError(err).Error("Failed to start SMTP server") + } + }) }) defer bridge.goLoad() @@ -545,7 +556,7 @@ func (bridge *Bridge) onStatusDown(ctx context.Context) { } func loadTLSConfig(vault *vault.Vault) (*tls.Config, error) { - cert, err := tls.X509KeyPair(vault.GetBridgeTLSCert(), vault.GetBridgeTLSKey()) + cert, err := tls.X509KeyPair(vault.GetBridgeTLSCert()) if err != nil { return nil, err } diff --git a/internal/bridge/bridge_test.go b/internal/bridge/bridge_test.go index 5acbbf9f..992d241f 100644 --- a/internal/bridge/bridge_test.go +++ b/internal/bridge/bridge_test.go @@ -21,6 +21,7 @@ import ( "context" "crypto/tls" "fmt" + "net" "net/http" "os" "path/filepath" @@ -29,6 +30,7 @@ import ( "time" "github.com/Masterminds/semver/v3" + "github.com/ProtonMail/gluon/imap" "github.com/ProtonMail/go-proton-api" "github.com/ProtonMail/go-proton-api/server" "github.com/ProtonMail/go-proton-api/server/backend" @@ -121,8 +123,11 @@ func TestBridge_Focus(t *testing.T) { raiseCh, done := bridge.GetEvents(events.Raise{}) defer done() + settingsFolder, err := locator.ProvideSettingsPath() + require.NoError(t, err) + // Simulate a focus event. - focus.TryRaise() + focus.TryRaise(settingsFolder) // Wait for the event. require.IsType(t, events.Raise{}, <-raiseCh) @@ -496,6 +501,21 @@ func TestBridge_InitGluonDirectory(t *testing.T) { }) } +func TestBridge_LoginFailed(t *testing.T) { + withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, vaultKey []byte) { + withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { + failCh, done := chToType[events.Event, events.IMAPLoginFailed](bridge.GetEvents(events.IMAPLoginFailed{})) + defer done() + + imapClient, err := client.Dial(net.JoinHostPort(constants.Host, fmt.Sprint(bridge.GetIMAPPort()))) + require.NoError(t, err) + + require.Error(t, imapClient.Login("badUser", "badPass")) + require.Equal(t, "badUser", (<-failCh).Username) + }) + }) +} + func TestBridge_ChangeCacheDirectory(t *testing.T) { withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, vaultKey []byte) { userID, addrID, err := s.CreateUser("imap", password) @@ -657,6 +677,9 @@ func withMocks(t *testing.T, tests func(*bridge.Mocks)) { tests(mocks) } +// Needs to be global to survive bridge shutdown/startup in unit tests as they happen to fast. +var testUIDValidityGenerator = imap.DefaultEpochUIDValidityGenerator() + // withBridge creates a new bridge which points to the given API URL and uses the given keychain, and closes it when done. func withBridgeNoMocks( ctx context.Context, @@ -702,6 +725,7 @@ func withBridgeNoMocks( mocks.ProxyCtl, mocks.CrashHandler, mocks.Reporter, + testUIDValidityGenerator, // The logging stuff. os.Getenv("BRIDGE_LOG_IMAP_CLIENT") == "1", @@ -713,6 +737,10 @@ func withBridgeNoMocks( // Wait for bridge to finish loading users. waitForEvent(t, eventCh, events.AllUsersLoaded{}) + // Wait for bridge to start the IMAP server. + waitForEvent(t, eventCh, events.IMAPServerReady{}) + // Wait for bridge to start the SMTP server. + waitForEvent(t, eventCh, events.SMTPServerReady{}) // Set random IMAP and SMTP ports for the tests. require.NoError(t, bridge.SetIMAPPort(0)) @@ -742,7 +770,7 @@ func withBridge( }) } -func waitForEvent[T any](t *testing.T, eventCh <-chan events.Event, wantEvent T) { +func waitForEvent[T any](t *testing.T, eventCh <-chan events.Event, _ T) { t.Helper() for event := range eventCh { diff --git a/internal/bridge/bug_report.go b/internal/bridge/bug_report.go index bffc6911..10fb30e0 100644 --- a/internal/bridge/bug_report.go +++ b/internal/bridge/bug_report.go @@ -37,7 +37,7 @@ const ( MaxCompressedFilesCount = 6 ) -func (bridge *Bridge) ReportBug(ctx context.Context, osType, osVersion, description, username, email, client string, attachLogs bool) error { //nolint:funlen +func (bridge *Bridge) ReportBug(ctx context.Context, osType, osVersion, description, username, email, client string, attachLogs bool) error { var account string if info, err := bridge.QueryUserInfo(username); err == nil { diff --git a/internal/bridge/errors.go b/internal/bridge/errors.go index 98377649..ecb897f4 100644 --- a/internal/bridge/errors.go +++ b/internal/bridge/errors.go @@ -22,10 +22,7 @@ import "errors" var ( ErrVaultInsecure = errors.New("the vault is insecure") ErrVaultCorrupt = errors.New("the vault is corrupt") - - ErrServeIMAP = errors.New("failed to serve IMAP") - ErrServeSMTP = errors.New("failed to serve SMTP") - ErrWatchUpdates = errors.New("failed to watch for updates") + ErrWatchUpdates = errors.New("failed to watch for updates") ErrNoSuchUser = errors.New("no such user") ErrUserAlreadyExists = errors.New("user already exists") diff --git a/internal/bridge/imap.go b/internal/bridge/imap.go index 052eba57..d6efff44 100644 --- a/internal/bridge/imap.go +++ b/internal/bridge/imap.go @@ -28,10 +28,13 @@ import ( "github.com/Masterminds/semver/v3" "github.com/ProtonMail/gluon" imapEvents "github.com/ProtonMail/gluon/events" + "github.com/ProtonMail/gluon/imap" "github.com/ProtonMail/gluon/reporter" "github.com/ProtonMail/gluon/store" + "github.com/ProtonMail/gluon/store/fallback_v0" "github.com/ProtonMail/proton-bridge/v3/internal/async" "github.com/ProtonMail/proton-bridge/v3/internal/constants" + "github.com/ProtonMail/proton-bridge/v3/internal/events" "github.com/ProtonMail/proton-bridge/v3/internal/logging" "github.com/ProtonMail/proton-bridge/v3/internal/user" "github.com/ProtonMail/proton-bridge/v3/internal/vault" @@ -44,26 +47,42 @@ const ( ) func (bridge *Bridge) serveIMAP() error { - if bridge.imapServer == nil { - return fmt.Errorf("no imap server instance running") - } + port, err := func() (int, error) { + if bridge.imapServer == nil { + return 0, fmt.Errorf("no IMAP server instance running") + } - logrus.Info("Starting IMAP server") + logrus.Info("Starting IMAP server") + + imapListener, err := newListener(bridge.vault.GetIMAPPort(), bridge.vault.GetIMAPSSL(), bridge.tlsConfig) + if err != nil { + return 0, fmt.Errorf("failed to create IMAP listener: %w", err) + } + + bridge.imapListener = imapListener + + if err := bridge.imapServer.Serve(context.Background(), bridge.imapListener); err != nil { + return 0, fmt.Errorf("failed to serve IMAP: %w", err) + } + + if err := bridge.vault.SetIMAPPort(getPort(imapListener.Addr())); err != nil { + return 0, fmt.Errorf("failed to store IMAP port in vault: %w", err) + } + + return getPort(imapListener.Addr()), nil + }() - imapListener, err := newListener(bridge.vault.GetIMAPPort(), bridge.vault.GetIMAPSSL(), bridge.tlsConfig) if err != nil { - return fmt.Errorf("failed to create IMAP listener: %w", err) + bridge.publish(events.IMAPServerError{ + Error: err, + }) + + return err } - bridge.imapListener = imapListener - - if err := bridge.imapServer.Serve(context.Background(), bridge.imapListener); err != nil { - return fmt.Errorf("failed to serve IMAP: %w", err) - } - - if err := bridge.vault.SetIMAPPort(getPort(imapListener.Addr())); err != nil { - return fmt.Errorf("failed to store IMAP port in vault: %w", err) - } + bridge.publish(events.IMAPServerReady{ + Port: port, + }) return nil } @@ -75,6 +94,8 @@ func (bridge *Bridge) restartIMAP() error { if err := bridge.imapListener.Close(); err != nil { return fmt.Errorf("failed to close IMAP listener: %w", err) } + + bridge.publish(events.IMAPServerStopped{}) } return bridge.serveIMAP() @@ -87,6 +108,7 @@ func (bridge *Bridge) closeIMAP(ctx context.Context) error { if err := bridge.imapServer.Close(ctx); err != nil { return fmt.Errorf("failed to close IMAP server: %w", err) } + bridge.imapServer = nil } @@ -96,12 +118,12 @@ func (bridge *Bridge) closeIMAP(ctx context.Context) error { } } + bridge.publish(events.IMAPServerStopped{}) + return nil } // addIMAPUser connects the given user to gluon. -// -//nolint:funlen func (bridge *Bridge) addIMAPUser(ctx context.Context, user *user.User) error { if bridge.imapServer == nil { return fmt.Errorf("no imap server instance running") @@ -242,6 +264,13 @@ func (bridge *Bridge) handleIMAPEvent(event imapEvents.Event) { if event.IMAPID.Name != "" && event.IMAPID.Version != "" { bridge.identifier.SetClient(event.IMAPID.Name, event.IMAPID.Version) } + + case imapEvents.LoginFailed: + logrus.WithFields(logrus.Fields{ + "sessionID": event.SessionID, + "username": event.Username, + }).Info("Received IMAP login failure notification") + bridge.publish(events.IMAPLoginFailed{Username: event.Username}) } } @@ -261,7 +290,6 @@ func ApplyGluonConfigPathSuffix(basePath string) string { return filepath.Join(basePath, "backend", "db") } -// nolint:funlen func newIMAPServer( gluonCacheDir, gluonConfigDir string, version *semver.Version, @@ -270,6 +298,7 @@ func newIMAPServer( logClient, logServer bool, eventCh chan<- imapEvents.Event, tasks *async.Group, + uidValidityGenerator imap.UIDValidityGenerator, ) (*gluon.Server, error) { gluonCacheDir = ApplyGluonCachePathSuffix(gluonCacheDir) gluonConfigDir = ApplyGluonConfigPathSuffix(gluonConfigDir) @@ -313,6 +342,7 @@ func newIMAPServer( gluon.WithLogger(imapClientLog, imapServerLog), getGluonVersionInfo(version), gluon.WithReporter(reporter), + gluon.WithUIDValidityGenerator(uidValidityGenerator), ) if err != nil { return nil, err @@ -348,7 +378,7 @@ func (*storeBuilder) New(path, userID string, passphrase []byte) (store.Store, e return store.NewOnDiskStore( filepath.Join(path, userID), passphrase, - store.WithCompressor(new(store.GZipCompressor)), + store.WithFallback(fallback_v0.NewOnDiskStoreV0WithCompressor(&fallback_v0.GZipCompressor{})), ) } diff --git a/internal/bridge/refresh_test.go b/internal/bridge/refresh_test.go index eb1f4159..ad4192f6 100644 --- a/internal/bridge/refresh_test.go +++ b/internal/bridge/refresh_test.go @@ -57,6 +57,7 @@ func TestBridge_Refresh(t *testing.T) { require.Equal(t, userID, (<-syncCh).UserID) }) + var uidValidities = make(map[string]uint32, len(names)) // If we then connect an IMAP client, it should see all the labels with UID validity of 1. withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(b *bridge.Bridge, mocks *bridge.Mocks) { mocks.Reporter.EXPECT().ReportMessageWithContext(gomock.Any(), gomock.Any()).AnyTimes() @@ -73,7 +74,7 @@ func TestBridge_Refresh(t *testing.T) { for _, name := range names { status, err := client.Select("Folders/"+name, false) require.NoError(t, err) - require.Equal(t, uint32(1000), status.UidValidity) + uidValidities[name] = status.UidValidity } }) @@ -106,7 +107,7 @@ func TestBridge_Refresh(t *testing.T) { for _, name := range names { status, err := client.Select("Folders/"+name, false) require.NoError(t, err) - require.Equal(t, uint32(1001), status.UidValidity) + require.Greater(t, status.UidValidity, uidValidities[name]) } }) }) diff --git a/internal/bridge/settings.go b/internal/bridge/settings.go index 6d276561..0ab4c5a0 100644 --- a/internal/bridge/settings.go +++ b/internal/bridge/settings.go @@ -25,11 +25,9 @@ import ( "path/filepath" "github.com/Masterminds/semver/v3" - "github.com/ProtonMail/proton-bridge/v3/internal/constants" "github.com/ProtonMail/proton-bridge/v3/internal/safe" "github.com/ProtonMail/proton-bridge/v3/internal/updater" "github.com/ProtonMail/proton-bridge/v3/internal/vault" - "github.com/ProtonMail/proton-bridge/v3/pkg/keychain" "github.com/sirupsen/logrus" ) @@ -131,26 +129,21 @@ func (bridge *Bridge) SetGluonDir(ctx context.Context, newGluonDir string) error return fmt.Errorf("new gluon dir is the same as the old one") } - if err := bridge.stopEventLoops(); err != nil { - return err + if err := bridge.closeIMAP(context.Background()); err != nil { + return fmt.Errorf("failed to close IMAP: %w", err) } - defer func() { - err := bridge.startEventLoops(ctx) - if err != nil { - panic(err) - } - }() if err := bridge.moveGluonCacheDir(currentGluonDir, newGluonDir); err != nil { logrus.WithError(err).Error("failed to move GluonCacheDir") + if err := bridge.vault.SetGluonDir(currentGluonDir); err != nil { - panic(err) + return fmt.Errorf("failed to revert GluonCacheDir: %w", err) } } gluonDataDir, err := bridge.GetGluonDataDir() if err != nil { - panic(fmt.Errorf("failed to get Gluon Database directory: %w", err)) + return fmt.Errorf("failed to get Gluon Database directory: %w", err) } imapServer, err := newIMAPServer( @@ -163,13 +156,24 @@ func (bridge *Bridge) SetGluonDir(ctx context.Context, newGluonDir string) error bridge.logIMAPServer, bridge.imapEventCh, bridge.tasks, + bridge.uidValidityGenerator, ) if err != nil { - panic(fmt.Errorf("failed to create new IMAP server: %w", err)) + return fmt.Errorf("failed to create new IMAP server: %w", err) } bridge.imapServer = imapServer + for _, user := range bridge.users { + if err := bridge.addIMAPUser(ctx, user); err != nil { + return fmt.Errorf("failed to add users to new IMAP server: %w", err) + } + } + + if err := bridge.serveIMAP(); err != nil { + return fmt.Errorf("failed to serve IMAP: %w", err) + } + return nil }, bridge.usersLock) } @@ -191,34 +195,6 @@ func (bridge *Bridge) moveGluonCacheDir(oldGluonDir, newGluonDir string) error { return nil } -func (bridge *Bridge) stopEventLoops() error { - if err := bridge.closeIMAP(context.Background()); err != nil { - return fmt.Errorf("failed to close IMAP: %w", err) - } - - if err := bridge.closeSMTP(); err != nil { - return fmt.Errorf("failed to close SMTP: %w", err) - } - return nil -} - -func (bridge *Bridge) startEventLoops(ctx context.Context) error { - for _, user := range bridge.users { - if err := bridge.addIMAPUser(ctx, user); err != nil { - return fmt.Errorf("failed to add users to new IMAP server: %w", err) - } - } - - if err := bridge.serveIMAP(); err != nil { - panic(fmt.Errorf("failed to serve IMAP: %w", err)) - } - - if err := bridge.serveSMTP(); err != nil { - panic(fmt.Errorf("failed to serve SMTP: %w", err)) - } - return nil -} - func (bridge *Bridge) GetProxyAllowed() bool { return bridge.vault.GetProxyAllowed() } @@ -332,6 +308,9 @@ func (bridge *Bridge) SetColorScheme(colorScheme string) error { return bridge.vault.SetColorScheme(colorScheme) } +// FactoryReset deletes all users, wipes the vault, and deletes all files. +// Note: it does not clear the keychain. The only entry in the keychain is the vault password, +// which we need at next startup to decrypt the vault. func (bridge *Bridge) FactoryReset(ctx context.Context) { // Delete all the users. safe.Lock(func() { @@ -348,22 +327,10 @@ func (bridge *Bridge) FactoryReset(ctx context.Context) { logrus.WithError(err).Error("Failed to reset vault") } - // Then delete all files. - if err := bridge.locator.Clear(); err != nil { + // Lastly, delete all files except the vault. + if err := bridge.locator.Clear(bridge.vault.Path()); err != nil { logrus.WithError(err).Error("Failed to clear data paths") } - - // Lastly clear the keychain. - vaultDir, err := bridge.locator.ProvideSettingsPath() - if err != nil { - logrus.WithError(err).Error("Failed to get vault dir") - } else if helper, err := vault.GetHelper(vaultDir); err != nil { - logrus.WithError(err).Error("Failed to get keychain helper") - } else if keychain, err := keychain.NewKeychain(helper, constants.KeyChainName); err != nil { - logrus.WithError(err).Error("Failed to get keychain") - } else if err := keychain.Clear(); err != nil { - logrus.WithError(err).Error("Failed to clear keychain") - } } func getPort(addr net.Addr) int { diff --git a/internal/bridge/smtp.go b/internal/bridge/smtp.go index 35642e6f..b6d95158 100644 --- a/internal/bridge/smtp.go +++ b/internal/bridge/smtp.go @@ -22,6 +22,7 @@ import ( "crypto/tls" "fmt" + "github.com/ProtonMail/proton-bridge/v3/internal/events" "github.com/ProtonMail/proton-bridge/v3/internal/logging" "github.com/ProtonMail/proton-bridge/v3/internal/constants" @@ -31,25 +32,41 @@ import ( ) func (bridge *Bridge) serveSMTP() error { - logrus.Info("Starting SMTP server") + port, err := func() (int, error) { + logrus.Info("Starting SMTP server") - smtpListener, err := newListener(bridge.vault.GetSMTPPort(), bridge.vault.GetSMTPSSL(), bridge.tlsConfig) - if err != nil { - return fmt.Errorf("failed to create SMTP listener: %w", err) - } - - bridge.smtpListener = smtpListener - - bridge.tasks.Once(func(context.Context) { - if err := bridge.smtpServer.Serve(smtpListener); err != nil { - logrus.WithError(err).Info("SMTP server stopped") + smtpListener, err := newListener(bridge.vault.GetSMTPPort(), bridge.vault.GetSMTPSSL(), bridge.tlsConfig) + if err != nil { + return 0, fmt.Errorf("failed to create SMTP listener: %w", err) } - }) - if err := bridge.vault.SetSMTPPort(getPort(smtpListener.Addr())); err != nil { - return fmt.Errorf("failed to store SMTP port in vault: %w", err) + bridge.smtpListener = smtpListener + + bridge.tasks.Once(func(context.Context) { + if err := bridge.smtpServer.Serve(smtpListener); err != nil { + logrus.WithError(err).Info("SMTP server stopped") + } + }) + + if err := bridge.vault.SetSMTPPort(getPort(smtpListener.Addr())); err != nil { + return 0, fmt.Errorf("failed to store SMTP port in vault: %w", err) + } + + return getPort(smtpListener.Addr()), nil + }() + + if err != nil { + bridge.publish(events.SMTPServerError{ + Error: err, + }) + + return err } + bridge.publish(events.SMTPServerReady{ + Port: port, + }) + return nil } @@ -60,6 +77,8 @@ func (bridge *Bridge) restartSMTP() error { return fmt.Errorf("failed to close SMTP: %w", err) } + bridge.publish(events.SMTPServerStopped{}) + bridge.smtpServer = newSMTPServer(bridge, bridge.tlsConfig, bridge.logSMTP) return bridge.serveSMTP() @@ -82,6 +101,8 @@ func (bridge *Bridge) closeSMTP() error { logrus.WithError(err).Debug("Failed to close SMTP server (expected -- we close the listener ourselves)") } + bridge.publish(events.SMTPServerStopped{}) + return nil } diff --git a/internal/bridge/sync_test.go b/internal/bridge/sync_test.go index 357cbab1..329a42c5 100644 --- a/internal/bridge/sync_test.go +++ b/internal/bridge/sync_test.go @@ -431,7 +431,7 @@ func createMessages(ctx context.Context, t *testing.T, c *proton.Client, addrID, _, ok := addrKRs[addrID] require.True(t, ok) - res, err := stream.Collect(ctx, c.ImportMessages( + str, err := c.ImportMessages( ctx, addrKRs[addrID], runtime.NumCPU(), @@ -446,7 +446,10 @@ func createMessages(ctx context.Context, t *testing.T, c *proton.Client, addrID, Message: message, } })..., - )) + ) + require.NoError(t, err) + + res, err := stream.Collect(ctx, str) require.NoError(t, err) return xslices.Map(res, func(res proton.ImportRes) string { diff --git a/internal/bridge/tls.go b/internal/bridge/tls.go index b62e3470..c5e50fce 100644 --- a/internal/bridge/tls.go +++ b/internal/bridge/tls.go @@ -18,5 +18,9 @@ package bridge func (bridge *Bridge) GetBridgeTLSCert() ([]byte, []byte) { - return bridge.vault.GetBridgeTLSCert(), bridge.vault.GetBridgeTLSKey() + return bridge.vault.GetBridgeTLSCert() +} + +func (bridge *Bridge) SetBridgeTLSCertPath(certPath, keyPath string) error { + return bridge.vault.SetBridgeTLSCertPath(certPath, keyPath) } diff --git a/internal/bridge/types.go b/internal/bridge/types.go index 4c79defc..3e779817 100644 --- a/internal/bridge/types.go +++ b/internal/bridge/types.go @@ -30,7 +30,7 @@ type Locator interface { ProvideGluonDataPath() (string, error) GetLicenseFilePath() string GetDependencyLicensesLink() string - Clear() error + Clear(...string) error } type Identifier interface { diff --git a/internal/bridge/updates.go b/internal/bridge/updates.go index 9b0ea252..4208ce5c 100644 --- a/internal/bridge/updates.go +++ b/internal/bridge/updates.go @@ -32,19 +32,7 @@ func (bridge *Bridge) CheckForUpdates() { } func (bridge *Bridge) InstallUpdate(version updater.VersionInfo) { - log := logrus.WithFields(logrus.Fields{ - "version": version.Version, - "current": bridge.curVersion, - "channel": bridge.vault.GetUpdateChannel(), - }) - - select { - case bridge.installCh <- installJob{version: version, silent: false}: - log.Info("The update will be installed manually") - - default: - log.Info("An update is already being installed") - } + bridge.installCh <- installJob{version: version, silent: false} } func (bridge *Bridge) handleUpdate(version updater.VersionInfo) { @@ -89,17 +77,7 @@ func (bridge *Bridge) handleUpdate(version updater.VersionInfo) { default: safe.RLock(func() { - if version.Version.GreaterThan(bridge.newVersion) { - log.Info("An update is available") - - select { - case bridge.installCh <- installJob{version: version, silent: true}: - log.Info("The update will be installed silently") - - default: - log.Info("An update is already being installed") - } - } + bridge.installCh <- installJob{version: version, silent: true} }, bridge.newVersionLock) } } @@ -117,6 +95,12 @@ func (bridge *Bridge) installUpdate(ctx context.Context, job installJob) { "channel": bridge.vault.GetUpdateChannel(), }) + if !job.version.Version.GreaterThan(bridge.newVersion) { + return + } + + log.WithField("silent", job.silent).Info("An update is available") + bridge.publish(events.UpdateAvailable{ Version: job.version, Compatible: true, @@ -142,6 +126,7 @@ func (bridge *Bridge) installUpdate(ctx context.Context, job installJob) { Silent: job.silent, Error: err, }) + default: log.Info("The update was installed successfully") diff --git a/internal/bridge/user.go b/internal/bridge/user.go index 5242ef70..59f8f00c 100644 --- a/internal/bridge/user.go +++ b/internal/bridge/user.go @@ -434,6 +434,7 @@ func (bridge *Bridge) loadUser(ctx context.Context, user *vault.User) error { logrus.WithError(err).Warn("Failed to clear user secrets") } } + return fmt.Errorf("failed to create API client: %w", err) } @@ -516,8 +517,8 @@ func (bridge *Bridge) addUserWithVault( bridge.reporter, apiUser, bridge.crashHandler, - bridge.vault.SyncWorkers(), bridge.vault.GetShowAllMail(), + bridge.vault.GetMaxSyncMemory(), ) if err != nil { return fmt.Errorf("failed to create user: %w", err) diff --git a/internal/bridge/user_event_test.go b/internal/bridge/user_event_test.go index 490bd597..c4404f0b 100644 --- a/internal/bridge/user_event_test.go +++ b/internal/bridge/user_event_test.go @@ -20,18 +20,23 @@ package bridge_test import ( "context" "fmt" + "net" "net/http" + "net/mail" "strings" "sync/atomic" "testing" "time" + "github.com/ProtonMail/gluon/rfc822" "github.com/ProtonMail/go-proton-api" "github.com/ProtonMail/go-proton-api/server" + "github.com/ProtonMail/gopenpgp/v2/crypto" "github.com/ProtonMail/proton-bridge/v3/internal/bridge" "github.com/ProtonMail/proton-bridge/v3/internal/constants" "github.com/ProtonMail/proton-bridge/v3/internal/events" "github.com/ProtonMail/proton-bridge/v3/internal/user" + "github.com/bradenaw/juniper/stream" "github.com/bradenaw/juniper/xslices" "github.com/emersion/go-imap" "github.com/emersion/go-imap/client" @@ -192,12 +197,13 @@ func TestBridge_User_BadMessage_NoBadEvent(t *testing.T) { var messageIDs []string + // Create 10 more messages for the user, generating events. + withClient(ctx, t, s, "user", password, func(ctx context.Context, c *proton.Client) { + messageIDs = createNumMessages(ctx, t, c, addrID, proton.InboxLabel, 10) + }) + // If bridge attempts to sync the new messages, it should get a BadRequest error. s.AddStatusHook(func(req *http.Request) (int, bool) { - if len(messageIDs) < 3 { - return 0, false - } - if strings.Contains(req.URL.Path, "/mail/v4/messages/"+messageIDs[2]) { return http.StatusUnprocessableEntity, true } @@ -205,11 +211,6 @@ func TestBridge_User_BadMessage_NoBadEvent(t *testing.T) { return 0, false }) - // Create 10 more messages for the user, generating events. - withClient(ctx, t, s, "user", password, func(ctx context.Context, c *proton.Client) { - messageIDs = createNumMessages(ctx, t, c, addrID, proton.InboxLabel, 10) - }) - // Remove messages withClient(ctx, t, s, "user", password, func(ctx context.Context, c *proton.Client) { require.NoError(t, c.DeleteMessage(ctx, messageIDs...)) @@ -374,6 +375,396 @@ func TestBridge_User_Network_NoBadEvents(t *testing.T) { }) } +func TestBridge_User_DropConn_NoBadEvent(t *testing.T) { + l, err := net.Listen("tcp", "127.0.0.1:0") + require.NoError(t, err) + + dropListener := proton.NewListener(l, proton.NewDropConn) + defer func() { _ = dropListener.Close() }() + + withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) { + // Create a user. + _, addrID, err := s.CreateUser("user", password) + require.NoError(t, err) + + // Create 10 messages for the user. + withClient(ctx, t, s, "user", password, func(ctx context.Context, c *proton.Client) { + createNumMessages(ctx, t, c, addrID, proton.InboxLabel, 10) + }) + + withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { + userLoginAndSync(ctx, t, bridge, "user", password) + + mocks.Reporter.EXPECT().ReportMessageWithContext(gomock.Any(), gomock.Any()).AnyTimes() + + // Create 10 more messages for the user, generating events. + withClient(ctx, t, s, "user", password, func(ctx context.Context, c *proton.Client) { + createNumMessages(ctx, t, c, addrID, proton.InboxLabel, 10) + }) + + var count int + + // The first 10 times bridge attempts to sync any of the messages, drop the connection. + s.AddStatusHook(func(req *http.Request) (int, bool) { + if strings.Contains(req.URL.Path, "/mail/v4/messages") { + if count++; count < 10 { + dropListener.DropAll() + } + } + + return 0, false + }) + + info, err := bridge.QueryUserInfo("user") + require.NoError(t, err) + + client, err := client.Dial(fmt.Sprintf("%v:%v", constants.Host, bridge.GetIMAPPort())) + require.NoError(t, err) + require.NoError(t, client.Login(info.Addresses[0], string(info.BridgePass))) + defer func() { _ = client.Logout() }() + + // The IMAP client will eventually see 20 messages. + require.Eventually(t, func() bool { + status, err := client.Status("INBOX", []imap.StatusItem{imap.StatusMessages}) + return err == nil && status.Messages == 20 + }, 10*time.Second, 100*time.Millisecond) + }) + }, server.WithListener(dropListener)) +} + +func TestBridge_User_UpdateDraftAndCreateOtherMessage(t *testing.T) { + withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) { + // Create a bridge user. + _, _, err := s.CreateUser("user", password) + require.NoError(t, err) + + // Initially sync the user. + withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { + userLoginAndSync(ctx, t, bridge, "user", password) + }) + + withClient(ctx, t, s, "user", password, func(ctx context.Context, c *proton.Client) { + user, err := c.GetUser(ctx) + require.NoError(t, err) + + addrs, err := c.GetAddresses(ctx) + require.NoError(t, err) + + salts, err := c.GetSalts(ctx) + require.NoError(t, err) + + keyPass, err := salts.SaltForKey(password, user.Keys.Primary().ID) + require.NoError(t, err) + + _, addrKRs, err := proton.Unlock(user, addrs, keyPass) + require.NoError(t, err) + + // Create a draft (generating a "create draft message" event). + draft, err := c.CreateDraft(ctx, addrKRs[addrs[0].ID], proton.CreateDraftReq{ + Message: proton.DraftTemplate{ + Subject: "subject", + Sender: &mail.Address{Name: "sender", Address: addrs[0].Email}, + Body: "body", + MIMEType: rfc822.TextPlain, + }, + }) + require.NoError(t, err) + + // Process those events + withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { + userContinueEventProcess(ctx, t, s, bridge) + }) + + // Update the draft (generating an "update draft message" event). + require.NoError(t, getErr(c.UpdateDraft(ctx, draft.ID, addrKRs[addrs[0].ID], proton.UpdateDraftReq{ + Message: proton.DraftTemplate{ + Subject: "subject 2", + Sender: &mail.Address{Name: "sender", Address: addrs[0].Email}, + Body: "body 2", + MIMEType: rfc822.TextPlain, + }, + }))) + + // Import a message (generating a "create message" event). + str, err := c.ImportMessages(ctx, addrKRs[addrs[0].ID], 1, 1, proton.ImportReq{ + Metadata: proton.ImportMetadata{ + AddressID: addrs[0].ID, + Flags: proton.MessageFlagReceived, + }, + Message: []byte("From: someone@example.com\r\nTo: blabla@example.com\r\n\r\nhello"), + }) + require.NoError(t, err) + + res, err := stream.Collect(ctx, str) + require.NoError(t, err) + + // Process those events. + withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { + userContinueEventProcess(ctx, t, s, bridge) + }) + + // Update the imported message (generating an "update message" event). + require.NoError(t, c.MarkMessagesUnread(ctx, res[0].MessageID)) + + // Process those events. + withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { + userContinueEventProcess(ctx, t, s, bridge) + }) + }) + }) +} + +func TestBridge_User_SendDraftRemoveDraftFlag(t *testing.T) { + withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) { + // Create a bridge user. + _, _, err := s.CreateUser("user", password) + require.NoError(t, err) + + // Initially sync the user. + withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { + userLoginAndSync(ctx, t, bridge, "user", password) + }) + + withClient(ctx, t, s, "user", password, func(ctx context.Context, c *proton.Client) { + user, err := c.GetUser(ctx) + require.NoError(t, err) + + addrs, err := c.GetAddresses(ctx) + require.NoError(t, err) + + salts, err := c.GetSalts(ctx) + require.NoError(t, err) + + keyPass, err := salts.SaltForKey(password, user.Keys.Primary().ID) + require.NoError(t, err) + + _, addrKRs, err := proton.Unlock(user, addrs, keyPass) + require.NoError(t, err) + + // Create a draft (generating a "create draft message" event). + draft, err := c.CreateDraft(ctx, addrKRs[addrs[0].ID], proton.CreateDraftReq{ + Message: proton.DraftTemplate{ + Subject: "subject", + ToList: []*mail.Address{{Address: addrs[0].Email}}, + Sender: &mail.Address{Name: "sender", Address: addrs[0].Email}, + Body: "body", + MIMEType: rfc822.TextPlain, + }, + }) + require.NoError(t, err) + + // Process those events + withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { + userContinueEventProcess(ctx, t, s, bridge) + + info, err := bridge.QueryUserInfo("user") + require.NoError(t, err) + + client, err := client.Dial(fmt.Sprintf("%v:%v", constants.Host, bridge.GetIMAPPort())) + require.NoError(t, err) + require.NoError(t, client.Login(info.Addresses[0], string(info.BridgePass))) + defer func() { _ = client.Logout() }() + + messages, err := clientFetch(client, "Drafts") + require.NoError(t, err) + require.Len(t, messages, 1) + require.Contains(t, messages[0].Flags, imap.DraftFlag) + }) + + // Send the draft (generating an "update message" event). + { + pubKeys, recType, err := c.GetPublicKeys(ctx, addrs[0].Email) + require.NoError(t, err) + require.Equal(t, recType, proton.RecipientTypeInternal) + + var req proton.SendDraftReq + + require.NoError(t, req.AddTextPackage(addrKRs[addrs[0].ID], "body", rfc822.TextPlain, map[string]proton.SendPreferences{ + addrs[0].Email: { + Encrypt: true, + PubKey: must(crypto.NewKeyRing(must(crypto.NewKeyFromArmored(pubKeys[0].PublicKey)))), + SignatureType: proton.DetachedSignature, + EncryptionScheme: proton.InternalScheme, + MIMEType: rfc822.TextPlain, + }, + }, nil)) + + require.NoError(t, getErr(c.SendDraft(ctx, draft.ID, req))) + } + + // Process those events; the draft will move to the sent folder and lose the draft flag. + withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { + userContinueEventProcess(ctx, t, s, bridge) + + info, err := bridge.QueryUserInfo("user") + require.NoError(t, err) + + client, err := client.Dial(fmt.Sprintf("%v:%v", constants.Host, bridge.GetIMAPPort())) + require.NoError(t, err) + require.NoError(t, client.Login(info.Addresses[0], string(info.BridgePass))) + defer func() { _ = client.Logout() }() + + messages, err := clientFetch(client, "Sent") + require.NoError(t, err) + require.Len(t, messages, 1) + require.NotContains(t, messages[0].Flags, imap.DraftFlag) + }) + }) + }) +} + +func TestBridge_User_DisableEnableAddress(t *testing.T) { + withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) { + // Create a user. + userID, _, err := s.CreateUser("user", password) + require.NoError(t, err) + + // Create an additional address for the user. + aliasID, err := s.CreateAddress(userID, "alias@"+s.GetDomain(), password) + require.NoError(t, err) + + withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { + require.NoError(t, getErr(bridge.LoginFull(ctx, "user", password, nil, nil))) + + // Initially we should list the address. + info, err := bridge.QueryUserInfo("user") + require.NoError(t, err) + require.Contains(t, info.Addresses, "alias@"+s.GetDomain()) + }) + + // Disable the address. + withClient(ctx, t, s, "user", password, func(ctx context.Context, c *proton.Client) { + require.NoError(t, c.DisableAddress(ctx, aliasID)) + }) + + withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { + // Eventually we shouldn't list the address. + require.Eventually(t, func() bool { + info, err := bridge.QueryUserInfo("user") + require.NoError(t, err) + + return xslices.Index(info.Addresses, "alias@"+s.GetDomain()) < 0 + }, 5*time.Second, 100*time.Millisecond) + }) + + // Enable the address. + withClient(ctx, t, s, "user", password, func(ctx context.Context, c *proton.Client) { + require.NoError(t, c.EnableAddress(ctx, aliasID)) + }) + + withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { + // Eventually we should list the address. + require.Eventually(t, func() bool { + info, err := bridge.QueryUserInfo("user") + require.NoError(t, err) + + return xslices.Index(info.Addresses, "alias@"+s.GetDomain()) >= 0 + }, 5*time.Second, 100*time.Millisecond) + }) + }) +} + +func TestBridge_User_CreateDisabledAddress(t *testing.T) { + withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) { + // Create a user. + userID, _, err := s.CreateUser("user", password) + require.NoError(t, err) + + // Create an additional address for the user. + aliasID, err := s.CreateAddress(userID, "alias@"+s.GetDomain(), password) + require.NoError(t, err) + + // Immediately disable the address. + withClient(ctx, t, s, "user", password, func(ctx context.Context, c *proton.Client) { + require.NoError(t, c.DisableAddress(ctx, aliasID)) + }) + + withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { + require.NoError(t, getErr(bridge.LoginFull(ctx, "user", password, nil, nil))) + + // Initially we shouldn't list the address. + info, err := bridge.QueryUserInfo("user") + require.NoError(t, err) + require.NotContains(t, info.Addresses, "alias@"+s.GetDomain()) + }) + }) +} + +func TestBridge_User_HandleParentLabelRename(t *testing.T) { + withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) { + withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { + require.NoError(t, getErr(bridge.LoginFull(ctx, username, password, nil, nil))) + + info, err := bridge.QueryUserInfo(username) + require.NoError(t, err) + + client, err := client.Dial(fmt.Sprintf("%v:%v", constants.Host, bridge.GetIMAPPort())) + require.NoError(t, err) + require.NoError(t, client.Login(info.Addresses[0], string(info.BridgePass))) + defer func() { _ = client.Logout() }() + + withClient(ctx, t, s, username, password, func(ctx context.Context, c *proton.Client) { + parentName := uuid.NewString() + childName := uuid.NewString() + + // Create a folder. + parentLabel, err := c.CreateLabel(ctx, proton.CreateLabelReq{ + Name: parentName, + Type: proton.LabelTypeFolder, + Color: "#f66", + }) + require.NoError(t, err) + + // Wait for the parent folder to be created. + require.Eventually(t, func() bool { + return xslices.IndexFunc(clientList(client), func(mailbox *imap.MailboxInfo) bool { + return mailbox.Name == fmt.Sprintf("Folders/%v", parentName) + }) >= 0 + }, 100*user.EventPeriod, user.EventPeriod) + + // Create a subfolder. + childLabel, err := c.CreateLabel(ctx, proton.CreateLabelReq{ + Name: childName, + Type: proton.LabelTypeFolder, + Color: "#f66", + ParentID: parentLabel.ID, + }) + require.NoError(t, err) + require.Equal(t, parentLabel.ID, childLabel.ParentID) + + // Wait for the parent folder to be created. + require.Eventually(t, func() bool { + return xslices.IndexFunc(clientList(client), func(mailbox *imap.MailboxInfo) bool { + return mailbox.Name == fmt.Sprintf("Folders/%v/%v", parentName, childName) + }) >= 0 + }, 100*user.EventPeriod, user.EventPeriod) + + newParentName := uuid.NewString() + + // Rename the parent folder. + require.NoError(t, getErr(c.UpdateLabel(ctx, parentLabel.ID, proton.UpdateLabelReq{ + Color: "#f66", + Name: newParentName, + }))) + + // Wait for the parent folder to be renamed. + require.Eventually(t, func() bool { + return xslices.IndexFunc(clientList(client), func(mailbox *imap.MailboxInfo) bool { + return mailbox.Name == fmt.Sprintf("Folders/%v", newParentName) + }) >= 0 + }, 100*user.EventPeriod, user.EventPeriod) + + // Wait for the child folder to be renamed. + require.Eventually(t, func() bool { + return xslices.IndexFunc(clientList(client), func(mailbox *imap.MailboxInfo) bool { + return mailbox.Name == fmt.Sprintf("Folders/%v/%v", newParentName, childName) + }) >= 0 + }, 100*user.EventPeriod, user.EventPeriod) + }) + }) + }) +} + // userLoginAndSync logs in user and waits until user is fully synced. func userLoginAndSync( ctx context.Context, diff --git a/internal/bridge/user_events.go b/internal/bridge/user_events.go index 267080a4..5873de8c 100644 --- a/internal/bridge/user_events.go +++ b/internal/bridge/user_events.go @@ -37,9 +37,14 @@ func (bridge *Bridge) handleUserEvent(ctx context.Context, user *user.User, even return fmt.Errorf("failed to handle user address created event: %w", err) } - case events.UserAddressUpdated: - if err := bridge.handleUserAddressUpdated(ctx, user, event); err != nil { - return fmt.Errorf("failed to handle user address updated event: %w", err) + case events.UserAddressEnabled: + if err := bridge.handleUserAddressEnabled(ctx, user, event); err != nil { + return fmt.Errorf("failed to handle user address enabled event: %w", err) + } + + case events.UserAddressDisabled: + if err := bridge.handleUserAddressDisabled(ctx, user, event); err != nil { + return fmt.Errorf("failed to handle user address disabled event: %w", err) } case events.UserAddressDeleted: @@ -66,55 +71,84 @@ func (bridge *Bridge) handleUserEvent(ctx context.Context, user *user.User, even } func (bridge *Bridge) handleUserAddressCreated(ctx context.Context, user *user.User, event events.UserAddressCreated) error { - if user.GetAddressMode() == vault.SplitMode { - if bridge.imapServer == nil { - return fmt.Errorf("no imap server instance running") - } + if user.GetAddressMode() != vault.SplitMode { + return nil + } - gluonID, err := bridge.imapServer.AddUser(ctx, user.NewIMAPConnector(event.AddressID), user.GluonKey()) - if err != nil { - return fmt.Errorf("failed to add user to IMAP server: %w", err) - } + if bridge.imapServer == nil { + return fmt.Errorf("no imap server instance running") + } - if err := user.SetGluonID(event.AddressID, gluonID); err != nil { - return fmt.Errorf("failed to set gluon ID: %w", err) - } + gluonID, err := bridge.imapServer.AddUser(ctx, user.NewIMAPConnector(event.AddressID), user.GluonKey()) + if err != nil { + return fmt.Errorf("failed to add user to IMAP server: %w", err) + } + + if err := user.SetGluonID(event.AddressID, gluonID); err != nil { + return fmt.Errorf("failed to set gluon ID: %w", err) } return nil } -// GODT-1948: Handle addresses that have been disabled! -func (bridge *Bridge) handleUserAddressUpdated(_ context.Context, user *user.User, _ events.UserAddressUpdated) error { - switch user.GetAddressMode() { - case vault.CombinedMode: - return fmt.Errorf("not implemented") +func (bridge *Bridge) handleUserAddressEnabled(ctx context.Context, user *user.User, event events.UserAddressEnabled) error { + if user.GetAddressMode() != vault.SplitMode { + return nil + } - case vault.SplitMode: - return fmt.Errorf("not implemented") + gluonID, err := bridge.imapServer.AddUser(ctx, user.NewIMAPConnector(event.AddressID), user.GluonKey()) + if err != nil { + return fmt.Errorf("failed to add user to IMAP server: %w", err) + } + + if err := user.SetGluonID(event.AddressID, gluonID); err != nil { + return fmt.Errorf("failed to set gluon ID: %w", err) + } + + return nil +} + +func (bridge *Bridge) handleUserAddressDisabled(ctx context.Context, user *user.User, event events.UserAddressDisabled) error { + if user.GetAddressMode() != vault.SplitMode { + return nil + } + + gluonID, ok := user.GetGluonID(event.AddressID) + if !ok { + return fmt.Errorf("gluon ID not found for address %s", event.AddressID) + } + + if err := bridge.imapServer.RemoveUser(ctx, gluonID, true); err != nil { + return fmt.Errorf("failed to remove user from IMAP server: %w", err) + } + + if err := user.RemoveGluonID(event.AddressID, gluonID); err != nil { + return fmt.Errorf("failed to remove gluon ID for address: %w", err) } return nil } func (bridge *Bridge) handleUserAddressDeleted(ctx context.Context, user *user.User, event events.UserAddressDeleted) error { - if user.GetAddressMode() == vault.SplitMode { - if bridge.imapServer == nil { - return fmt.Errorf("no imap server instance running") - } + if user.GetAddressMode() != vault.SplitMode { + return nil + } - gluonID, ok := user.GetGluonID(event.AddressID) - if !ok { - return fmt.Errorf("gluon ID not found for address %s", event.AddressID) - } + if bridge.imapServer == nil { + return fmt.Errorf("no imap server instance running") + } - if err := bridge.imapServer.RemoveUser(ctx, gluonID, true); err != nil { - return fmt.Errorf("failed to remove user from IMAP server: %w", err) - } + gluonID, ok := user.GetGluonID(event.AddressID) + if !ok { + return fmt.Errorf("gluon ID not found for address %s", event.AddressID) + } - if err := user.RemoveGluonID(event.AddressID, gluonID); err != nil { - return fmt.Errorf("failed to remove gluon ID for address: %w", err) - } + if err := bridge.imapServer.RemoveUser(ctx, gluonID, true); err != nil { + return fmt.Errorf("failed to remove user from IMAP server: %w", err) + } + + if err := user.RemoveGluonID(event.AddressID, gluonID); err != nil { + return fmt.Errorf("failed to remove gluon ID for address: %w", err) } return nil diff --git a/internal/bridge/user_test.go b/internal/bridge/user_test.go index 4c70f3a9..417e7bd7 100644 --- a/internal/bridge/user_test.go +++ b/internal/bridge/user_test.go @@ -20,6 +20,8 @@ package bridge_test import ( "context" "fmt" + "net" + "net/http" "testing" "time" @@ -61,6 +63,50 @@ func TestBridge_Login(t *testing.T) { }) } +func TestBridge_Login_DropConn(t *testing.T) { + l, err := net.Listen("tcp", "127.0.0.1:0") + require.NoError(t, err) + + dropListener := proton.NewListener(l, proton.NewDropConn) + defer func() { _ = dropListener.Close() }() + + withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) { + withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { + // Login the user. + userID, err := bridge.LoginFull(ctx, username, password, nil, nil) + require.NoError(t, err) + + // The user is now connected. + require.Equal(t, []string{userID}, bridge.GetUserIDs()) + require.Equal(t, []string{userID}, getConnectedUserIDs(t, bridge)) + }) + + // Whether to allow the user to be created. + var allowUser bool + + s.AddStatusHook(func(req *http.Request) (int, bool) { + // Drop any request to the users endpoint. + if !allowUser && req.URL.Path == "/core/v4/users" { + dropListener.DropAll() + } + + // After the ping request, allow the user to be created. + if req.URL.Path == "/tests/ping" { + allowUser = true + } + + return 0, false + }) + + withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { + // The user is eventually connected. + require.Eventually(t, func() bool { + return len(bridge.GetUserIDs()) == 1 && len(getConnectedUserIDs(t, bridge)) == 1 + }, 5*time.Second, 100*time.Millisecond) + }) + }, server.WithListener(dropListener)) +} + func TestBridge_LoginTwice(t *testing.T) { withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, storeKey []byte) { withBridge(ctx, t, s.GetHostURL(), netCtl, locator, storeKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) { diff --git a/internal/events/address.go b/internal/events/address.go index 687ea578..96695e26 100644 --- a/internal/events/address.go +++ b/internal/events/address.go @@ -35,6 +35,30 @@ func (event UserAddressCreated) String() string { return fmt.Sprintf("UserAddressCreated: UserID: %s, AddressID: %s, Email: %s", event.UserID, event.AddressID, logging.Sensitive(event.Email)) } +type UserAddressEnabled struct { + eventBase + + UserID string + AddressID string + Email string +} + +func (event UserAddressEnabled) String() string { + return fmt.Sprintf("UserAddressEnabled: UserID: %s, AddressID: %s, Email: %s", event.UserID, event.AddressID, logging.Sensitive(event.Email)) +} + +type UserAddressDisabled struct { + eventBase + + UserID string + AddressID string + Email string +} + +func (event UserAddressDisabled) String() string { + return fmt.Sprintf("UserAddressDisabled: UserID: %s, AddressID: %s, Email: %s", event.UserID, event.AddressID, logging.Sensitive(event.Email)) +} + type UserAddressUpdated struct { eventBase diff --git a/internal/events/serve.go b/internal/events/serve.go new file mode 100644 index 00000000..a8f866b1 --- /dev/null +++ b/internal/events/serve.go @@ -0,0 +1,76 @@ +// Copyright (c) 2023 Proton AG +// +// This file is part of Proton Mail Bridge. +// +// Proton Mail Bridge is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Proton Mail Bridge is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Proton Mail Bridge. If not, see . + +package events + +import "fmt" + +type IMAPServerReady struct { + eventBase + + Port int +} + +func (event IMAPServerReady) String() string { + return fmt.Sprintf("IMAPServerReady: Port %d", event.Port) +} + +type IMAPServerStopped struct { + eventBase +} + +func (event IMAPServerStopped) String() string { + return "IMAPServerStopped" +} + +type IMAPServerError struct { + eventBase + + Error error +} + +func (event IMAPServerError) String() string { + return fmt.Sprintf("IMAPServerError: %v", event.Error) +} + +type SMTPServerReady struct { + eventBase + + Port int +} + +func (event SMTPServerReady) String() string { + return fmt.Sprintf("SMTPServerReady: Port %d", event.Port) +} + +type SMTPServerStopped struct { + eventBase +} + +func (event SMTPServerStopped) String() string { + return "SMTPServerStopped" +} + +type SMTPServerError struct { + eventBase + + Error error +} + +func (event SMTPServerError) String() string { + return fmt.Sprintf("SMTPServerError: %v", event.Error) +} diff --git a/internal/events/user.go b/internal/events/user.go index 172bd55b..0faf1d30 100644 --- a/internal/events/user.go +++ b/internal/events/user.go @@ -169,6 +169,29 @@ func (event AddressModeChanged) String() string { return fmt.Sprintf("AddressModeChanged: UserID: %s, AddressMode: %s", event.UserID, event.AddressMode) } +// UsedSpaceChanged is emitted when the storage space used by the user has changed. +type UsedSpaceChanged struct { + eventBase + + UserID string + + UsedSpace int +} + +func (event UsedSpaceChanged) String() string { + return fmt.Sprintf("UsedSpaceChanged: UserID: %s, UsedSpace: %v", event.UserID, event.UsedSpace) +} + +type IMAPLoginFailed struct { + eventBase + + Username string +} + +func (event IMAPLoginFailed) String() string { + return fmt.Sprintf("IMAPLoginFailed: Username: %s", event.Username) +} + type UncategorizedEventError struct { eventBase diff --git a/internal/focus/client.go b/internal/focus/client.go index 0c5d8834..773c2e9e 100644 --- a/internal/focus/client.go +++ b/internal/focus/client.go @@ -21,9 +21,11 @@ import ( "context" "fmt" "net" + "path/filepath" "github.com/Masterminds/semver/v3" "github.com/ProtonMail/proton-bridge/v3/internal/focus/proto" + "github.com/ProtonMail/proton-bridge/v3/internal/service" "github.com/sirupsen/logrus" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" @@ -32,10 +34,10 @@ import ( // TryRaise tries to raise the application by dialing the focus service. // It returns true if the service is running and the application was told to raise. -func TryRaise() bool { +func TryRaise(settingsPath string) bool { var raised bool - if err := withClientConn(context.Background(), func(ctx context.Context, client proto.FocusClient) error { + if err := withClientConn(context.Background(), settingsPath, func(ctx context.Context, client proto.FocusClient) error { if _, err := client.Raise(ctx, &emptypb.Empty{}); err != nil { return fmt.Errorf("failed to call client.Raise: %w", err) } @@ -53,10 +55,10 @@ func TryRaise() bool { // TryVersion tries to determine the version of the running application instance. // It returns the version and true if the version could be determined. -func TryVersion() (*semver.Version, bool) { +func TryVersion(settingsPath string) (*semver.Version, bool) { var version *semver.Version - if err := withClientConn(context.Background(), func(ctx context.Context, client proto.FocusClient) error { + if err := withClientConn(context.Background(), settingsPath, func(ctx context.Context, client proto.FocusClient) error { raw, err := client.Version(ctx, &emptypb.Empty{}) if err != nil { return fmt.Errorf("failed to call client.Version: %w", err) @@ -78,10 +80,15 @@ func TryVersion() (*semver.Version, bool) { return version, true } -func withClientConn(ctx context.Context, fn func(context.Context, proto.FocusClient) error) error { +func withClientConn(ctx context.Context, settingsPath string, fn func(context.Context, proto.FocusClient) error) error { + var config = service.Config{} + err := config.Load(filepath.Join(settingsPath, serverConfigFileName)) + if err != nil { + return err + } cc, err := grpc.DialContext( ctx, - net.JoinHostPort(Host, fmt.Sprint(Port)), + net.JoinHostPort(Host, fmt.Sprint(config.Port)), grpc.WithTransportCredentials(insecure.NewCredentials()), ) if err != nil { diff --git a/internal/focus/focus_test.go b/internal/focus/focus_test.go index b6dd359b..059d243d 100644 --- a/internal/focus/focus_test.go +++ b/internal/focus/focus_test.go @@ -18,19 +18,25 @@ package focus import ( + "os" "testing" "github.com/Masterminds/semver/v3" + "github.com/ProtonMail/proton-bridge/v3/internal/locations" "github.com/stretchr/testify/require" ) func TestFocus_Raise(t *testing.T) { + tmpDir := t.TempDir() + locations := locations.New(newTestLocationsProvider(tmpDir), "config-name") // Start the focus service. - service, err := NewService(semver.MustParse("1.2.3")) + service, err := NewService(locations, semver.MustParse("1.2.3")) require.NoError(t, err) + settingsFolder, err := locations.ProvideSettingsPath() + require.NoError(t, err) // Try to dial it, it should succeed. - require.True(t, TryRaise()) + require.True(t, TryRaise(settingsFolder)) // The service should report a raise call. <-service.GetRaiseCh() @@ -39,16 +45,60 @@ func TestFocus_Raise(t *testing.T) { service.Close() // Try to dial it, it should fail. - require.False(t, TryRaise()) + require.False(t, TryRaise(settingsFolder)) } func TestFocus_Version(t *testing.T) { + tmpDir := t.TempDir() + locations := locations.New(newTestLocationsProvider(tmpDir), "config-name") // Start the focus service. - _, err := NewService(semver.MustParse("1.2.3")) + _, err := NewService(locations, semver.MustParse("1.2.3")) + require.NoError(t, err) + + settingsFolder, err := locations.ProvideSettingsPath() require.NoError(t, err) // Try to dial it, it should succeed. - version, ok := TryVersion() + version, ok := TryVersion(settingsFolder) require.True(t, ok) require.Equal(t, "1.2.3", version.String()) } + +type TestLocationsProvider struct { + config, data, cache string +} + +func newTestLocationsProvider(dir string) *TestLocationsProvider { + config, err := os.MkdirTemp(dir, "config") + if err != nil { + panic(err) + } + + data, err := os.MkdirTemp(dir, "data") + if err != nil { + panic(err) + } + + cache, err := os.MkdirTemp(dir, "cache") + if err != nil { + panic(err) + } + + return &TestLocationsProvider{ + config: config, + data: data, + cache: cache, + } +} + +func (provider *TestLocationsProvider) UserConfig() string { + return provider.config +} + +func (provider *TestLocationsProvider) UserData() string { + return provider.data +} + +func (provider *TestLocationsProvider) UserCache() string { + return provider.cache +} diff --git a/internal/focus/service.go b/internal/focus/service.go index a7728dfb..ab2d0127 100644 --- a/internal/focus/service.go +++ b/internal/focus/service.go @@ -25,16 +25,16 @@ import ( "github.com/Masterminds/semver/v3" "github.com/ProtonMail/proton-bridge/v3/internal/focus/proto" + "github.com/ProtonMail/proton-bridge/v3/internal/service" "github.com/sirupsen/logrus" "google.golang.org/grpc" "google.golang.org/protobuf/types/known/emptypb" ) -// Host is the local host to listen on. -const Host = "127.0.0.1" - -// Port is the port to listen on. -var Port = 1042 // nolint:gochecknoglobals +const ( + Host = "127.0.0.1" + serverConfigFileName = "grpcFocusServerConfig.json" +) // Service is a gRPC service that can be used to raise the application. type Service struct { @@ -47,26 +47,39 @@ type Service struct { // NewService creates a new focus service. // It listens on the local host and port 1042 (by default). -func NewService(version *semver.Version) (*Service, error) { - service := &Service{ +func NewService(locator service.Locator, version *semver.Version) (*Service, error) { + serv := &Service{ server: grpc.NewServer(), raiseCh: make(chan struct{}, 1), version: version, } - proto.RegisterFocusServer(service.server, service) + proto.RegisterFocusServer(serv.server, serv) - if listener, err := net.Listen("tcp", net.JoinHostPort(Host, fmt.Sprint(Port))); err != nil { - logrus.WithError(err).Warn("Failed to start focus service") + if listener, err := net.Listen("tcp", net.JoinHostPort(Host, fmt.Sprint(0))); err != nil { + logrus.WithError(err).Warn("Failed to start focus serv") } else { + config := service.Config{} + // retrieve the port assigned by the system, so that we can put it in the config file. + address, ok := listener.Addr().(*net.TCPAddr) + if !ok { + return nil, fmt.Errorf("could not retrieve gRPC service listener address") + } + config.Port = address.Port + if path, err := service.SaveGRPCServerConfigFile(locator, &config, serverConfigFileName); err != nil { + logrus.WithError(err).WithField("path", path).Warn("Could not write focus gRPC service config file") + } else { + logrus.WithField("path", path).Info("Successfully saved gRPC Focus service config file") + } + go func() { - if err := service.server.Serve(listener); err != nil { + if err := serv.server.Serve(listener); err != nil { fmt.Printf("failed to serve: %v", err) } }() } - return service, nil + return serv, nil } // Raise implements the gRPC FocusService interface; it raises the application. diff --git a/internal/frontend/bridge-gui/FindQt.cmake b/internal/frontend/bridge-gui/FindQt.cmake index ab7f8a1a..351d7b7b 100644 --- a/internal/frontend/bridge-gui/FindQt.cmake +++ b/internal/frontend/bridge-gui/FindQt.cmake @@ -1,8 +1,8 @@ -find_program(QMAKE_EXE "qmake") +find_program(QMAKE_EXE NAMES "qmake" "qmake6") if (NOT QMAKE_EXE) - message(FATAL_ERROR "Could not locate qmake executable, make sur you have Qt 6 installed in that qmake is in your PATH environment variable.") + message(FATAL_ERROR "Could not locate qmake executable, make sure you have Qt 6 installed in that qmake is in your PATH environment variable.") endif() message(STATUS "Found qmake at ${QMAKE_EXE}") execute_process(COMMAND "${QMAKE_EXE}" -query QT_INSTALL_PREFIX OUTPUT_VARIABLE QT_DIR OUTPUT_STRIP_TRAILING_WHITESPACE) -set(CMAKE_PREFIX_PATH ${QT_DIR} ${CMAKE_PREFIX_PATH}) \ No newline at end of file +set(CMAKE_PREFIX_PATH ${QT_DIR} ${CMAKE_PREFIX_PATH}) diff --git a/internal/frontend/bridge-gui/bridge-gui-tester/GRPCServerWorker.cpp b/internal/frontend/bridge-gui/bridge-gui-tester/GRPCServerWorker.cpp index 94ca25d5..118e190b 100644 --- a/internal/frontend/bridge-gui/bridge-gui-tester/GRPCServerWorker.cpp +++ b/internal/frontend/bridge-gui/bridge-gui-tester/GRPCServerWorker.cpp @@ -20,6 +20,7 @@ #include "Cert.h" #include "GRPCService.h" #include +#include #include #include @@ -33,7 +34,6 @@ using namespace grpc; //**************************************************************************************************************************************************** GRPCServerWorker::GRPCServerWorker(QObject *parent) : Worker(parent) { - } @@ -81,7 +81,7 @@ void GRPCServerWorker::run() { config.port = port; QString err; - if (!config.save(grpcServerConfigPath(), &err)) { + if (!config.save(grpcServerConfigPath(bridgepp::userConfigDir()), &err)) { throw Exception(QString("Could not save gRPC server config. %1").arg(err)); } diff --git a/internal/frontend/bridge-gui/bridge-gui-tester/GRPCService.cpp b/internal/frontend/bridge-gui/bridge-gui-tester/GRPCService.cpp index 7ffd5424..74682adf 100644 --- a/internal/frontend/bridge-gui/bridge-gui-tester/GRPCService.cpp +++ b/internal/frontend/bridge-gui/bridge-gui-tester/GRPCService.cpp @@ -192,17 +192,6 @@ Status GRPCService::IsAllMailVisible(ServerContext *, Empty const *request, Bool } -//**************************************************************************************************************************************************** -/// \param[out] response The response. -/// \return The status for the call. -//**************************************************************************************************************************************************** -Status GRPCService::GoOs(ServerContext *, Empty const *, StringValue *response) { - app().log().debug(__FUNCTION__); - response->set_value(app().mainWindow().settingsTab().os().toStdString()); - return Status::OK; -} - - //**************************************************************************************************************************************************** /// \return The status for the call. //**************************************************************************************************************************************************** diff --git a/internal/frontend/bridge-gui/bridge-gui-tester/GRPCService.h b/internal/frontend/bridge-gui/bridge-gui-tester/GRPCService.h index c3c4aea4..0c03809b 100644 --- a/internal/frontend/bridge-gui/bridge-gui-tester/GRPCService.h +++ b/internal/frontend/bridge-gui/bridge-gui-tester/GRPCService.h @@ -51,7 +51,6 @@ public: // member functions. grpc::Status IsBetaEnabled(::grpc::ServerContext *, ::google::protobuf::Empty const *, ::google::protobuf::BoolValue *response) override; grpc::Status SetIsAllMailVisible(::grpc::ServerContext *context, ::google::protobuf::BoolValue const *request, ::google::protobuf::Empty *response) override; grpc::Status IsAllMailVisible(::grpc::ServerContext *context, ::google::protobuf::Empty const *request, ::google::protobuf::BoolValue *response) override; - grpc::Status GoOs(::grpc::ServerContext *, ::google::protobuf::Empty const *, ::google::protobuf::StringValue *response) override; grpc::Status TriggerReset(::grpc::ServerContext *, ::google::protobuf::Empty const *, ::google::protobuf::Empty *) override; grpc::Status Version(::grpc::ServerContext *, ::google::protobuf::Empty const *, ::google::protobuf::StringValue *response) override; grpc::Status LogsPath(::grpc::ServerContext *, ::google::protobuf::Empty const *, ::google::protobuf::StringValue *response) override; diff --git a/internal/frontend/bridge-gui/bridge-gui-tester/Tabs/UsersTab.cpp b/internal/frontend/bridge-gui/bridge-gui-tester/Tabs/UsersTab.cpp index 7c5fbce1..5715472c 100644 --- a/internal/frontend/bridge-gui/bridge-gui-tester/Tabs/UsersTab.cpp +++ b/internal/frontend/bridge-gui/bridge-gui-tester/Tabs/UsersTab.cpp @@ -52,7 +52,11 @@ UsersTab::UsersTab(QWidget *parent) connect(ui_.tableUserList, &QTableView::doubleClicked, this, &UsersTab::onEditUserButton); connect(ui_.buttonRemoveUser, &QPushButton::clicked, this, &UsersTab::onRemoveUserButton); connect(ui_.buttonUserBadEvent, &QPushButton::clicked, this, &UsersTab::onSendUserBadEvent); + connect(ui_.buttonImapLoginFailed, &QPushButton::clicked, this, &UsersTab::onSendIMAPLoginFailedEvent); + connect(ui_.buttonUsedBytesChanged, &QPushButton::clicked, this, &UsersTab::onSendUsedBytesChangedEvent); connect(ui_.checkUsernamePasswordError, &QCheckBox::toggled, this, &UsersTab::updateGUIState); + connect(ui_.checkSync, &QCheckBox::toggled, this, &UsersTab::onCheckSyncToggled); + connect(ui_.sliderSync, &QSlider::valueChanged, this, &UsersTab::onSliderSyncValueChanged); users_.append(randomUser()); @@ -155,16 +159,76 @@ void UsersTab::onSendUserBadEvent() { } +//**************************************************************************************************************************************************** +// +//**************************************************************************************************************************************************** +void UsersTab::onSendUsedBytesChangedEvent() { + SPUser const user = selectedUser(); + int const index = this->selectedIndex(); + + if (!user) { + app().log().error(QString("%1 failed. Unkown user.").arg(__FUNCTION__)); + return; + } + + if (UserState::Connected != user->state()) { + app().log().error(QString("%1 failed. User is not connected").arg(__FUNCTION__)); + } + + qint64 const usedBytes = qint64(ui_.spinUsedBytes->value()); + user->setUsedBytes(usedBytes); + users_.touch(index); + + GRPCService &grpc = app().grpc(); + if (grpc.isStreaming()) { + QString const userID = user->id(); + grpc.sendEvent(newUsedBytesChangedEvent(userID, usedBytes)); + } + + this->updateGUIState(); +} + + +//**************************************************************************************************************************************************** +// +//**************************************************************************************************************************************************** +void UsersTab::onSendIMAPLoginFailedEvent() { + GRPCService &grpc = app().grpc(); + if (grpc.isStreaming()) { + grpc.sendEvent(newIMAPLoginFailedEvent(ui_.editIMAPLoginFailedUsername->text())); + } + + this->updateGUIState(); +} + + //**************************************************************************************************************************************************** // //**************************************************************************************************************************************************** void UsersTab::updateGUIState() { SPUser const user = selectedUser(); bool const hasSelectedUser = user.get(); + UserState const state = user ? user->state() : UserState::SignedOut; + ui_.buttonEditUser->setEnabled(hasSelectedUser); ui_.buttonRemoveUser->setEnabled(hasSelectedUser); - ui_.groupBoxBadEvent->setEnabled(hasSelectedUser && (UserState::SignedOut != user->state())); + ui_.groupBoxBadEvent->setEnabled(hasSelectedUser && (UserState::SignedOut != state)); + ui_.groupBoxUsedSpace->setEnabled(hasSelectedUser && (UserState::Connected == state)); ui_.editUsernamePasswordError->setEnabled(ui_.checkUsernamePasswordError->isChecked()); + ui_.spinUsedBytes->setValue(user ? user->usedBytes() : 0.0); + ui_.groupboxSync->setEnabled(user.get()); + + if (user) + ui_.editIMAPLoginFailedUsername->setText(user->primaryEmailOrUsername()); + + QSignalBlocker b(ui_.checkSync); + bool const syncing = user && user->isSyncing(); + ui_.checkSync->setChecked(syncing); + b = QSignalBlocker(ui_.sliderSync); + ui_.sliderSync->setEnabled(syncing); + qint32 const progressPercent = syncing ? qint32(user->syncProgress() * 100.0f) : 0; + ui_.sliderSync->setValue(progressPercent); + ui_.labelSync->setText(syncing ? QString("%1%").arg(progressPercent) : "" ); } @@ -368,3 +432,44 @@ void UsersTab::processBadEventUserFeedback(QString const &userID, bool doResync) this->updateGUIState(); } + + +//**************************************************************************************************************************************************** +/// \param[in] checked Is the sync checkbox checked? +//**************************************************************************************************************************************************** +void UsersTab::onCheckSyncToggled(bool checked) { + SPUser const user = this->selectedUser(); + if ((!user) || (user->isSyncing() == checked)) { + return; + } + + user->setIsSyncing(checked); + user->setSyncProgress(0.0); + GRPCService &grpc = app().grpc(); + + // we do not apply delay for these event. + if (checked) { + grpc.sendEvent(newSyncStartedEvent(user->id())); + grpc.sendEvent(newSyncProgressEvent(user->id(), 0.0, 1, 1)); + } else { + grpc.sendEvent(newSyncFinishedEvent(user->id())); + } + + this->updateGUIState(); +} + + +//**************************************************************************************************************************************************** +/// \param[in] value The value for the slider. +//**************************************************************************************************************************************************** +void UsersTab::onSliderSyncValueChanged(int value) { + SPUser const user = this->selectedUser(); + if ((!user) || (!user->isSyncing()) || user->syncProgress() == value) { + return; + } + + double const progress = value / 100.0; + user->setSyncProgress(progress); + app().grpc().sendEvent(newSyncProgressEvent(user->id(), progress, 1, 1)); // we do not simulate elapsed & remaining. + this->updateGUIState(); +} diff --git a/internal/frontend/bridge-gui/bridge-gui-tester/Tabs/UsersTab.h b/internal/frontend/bridge-gui/bridge-gui-tester/Tabs/UsersTab.h index e137c2bc..8c7a5329 100644 --- a/internal/frontend/bridge-gui/bridge-gui-tester/Tabs/UsersTab.h +++ b/internal/frontend/bridge-gui/bridge-gui-tester/Tabs/UsersTab.h @@ -62,6 +62,10 @@ private slots: void onRemoveUserButton(); ///< Remove the currently selected user. void onSelectionChanged(QItemSelection, QItemSelection); ///< Slot for the change of the selection. void onSendUserBadEvent(); ///< Slot for the 'Send Bad Event Error' button. + void onSendUsedBytesChangedEvent(); ///< Slot for the 'Send Used Bytes Changed Event' button. + void onSendIMAPLoginFailedEvent(); ///< Slot for the 'Send IMAP Login failure Event' button. + void onCheckSyncToggled(bool checked); ///< Slot for the 'Synchronizing' check box. + void onSliderSyncValueChanged(int value); ///< Slot for the sync 'Progress' slider. void updateGUIState(); ///< Update the GUI state. private: // member functions. diff --git a/internal/frontend/bridge-gui/bridge-gui-tester/Tabs/UsersTab.ui b/internal/frontend/bridge-gui/bridge-gui-tester/Tabs/UsersTab.ui index 052e2546..40dfbaaf 100644 --- a/internal/frontend/bridge-gui/bridge-gui-tester/Tabs/UsersTab.ui +++ b/internal/frontend/bridge-gui/bridge-gui-tester/Tabs/UsersTab.ui @@ -6,8 +6,8 @@ 0 0 - 1225 - 717 + 1221 + 894 @@ -66,6 +66,52 @@ + + + + + 0 + 0 + + + + Sync + + + + + + + + Synchronizing + + + + + + + 0% + + + + + + + + + 100 + + + Qt::Horizontal + + + 10 + + + + + + @@ -80,13 +126,6 @@ - - - - Message: - - - @@ -96,18 +135,102 @@ - Bad event error. + + + + error message + + + + + + + Send + + + + + + + + 0 + 0 + + + + Used Bytes Changed + + - - - Send Bad Event Error - - + + + + + QAbstractSpinBox::NoButtons + + + 0 + + + 1000000000000000.000000000000000 + + + + + + + Send + + + + + + + + + + + + + 0 + 0 + + + + IMAP Login Failure + + + + + + + + + 200 + 0 + + + + + + + username or primary email + + + + + + + Send + + + + diff --git a/internal/frontend/bridge-gui/bridge-gui-tester/main.cpp b/internal/frontend/bridge-gui/bridge-gui-tester/main.cpp index 4011aa64..be65fdfb 100644 --- a/internal/frontend/bridge-gui/bridge-gui-tester/main.cpp +++ b/internal/frontend/bridge-gui/bridge-gui-tester/main.cpp @@ -85,7 +85,10 @@ int main(int argc, char **argv) { return exitCode; } catch (Exception const &e) { - QTextStream(stderr) << QString("A fatal error occurred: %1\n").arg(e.qwhat()); + QString message = e.qwhat(); + if (!e.details().isEmpty()) + message += "\n\nDetails:\n" + e.details(); + QTextStream(stderr) << QString("A fatal error occurred: %1\n").arg(message); return EXIT_FAILURE; } } diff --git a/internal/frontend/bridge-gui/bridge-gui/AppController.cpp b/internal/frontend/bridge-gui/bridge-gui/AppController.cpp index 25d8e1f4..0f2ad890 100644 --- a/internal/frontend/bridge-gui/bridge-gui/AppController.cpp +++ b/internal/frontend/bridge-gui/bridge-gui/AppController.cpp @@ -19,6 +19,7 @@ #include "AppController.h" #include "QMLBackend.h" #include "SentryUtils.h" +#include "Settings.h" #include #include #include @@ -48,10 +49,19 @@ AppController &app() { AppController::AppController() : backend_(std::make_unique()) , grpc_(std::make_unique()) - , log_(std::make_unique()) { + , log_(std::make_unique()) + , settings_(new Settings) { } + +//**************************************************************************************************************************************************** +// The following is in the implementation file because of unique pointers with incomplete types in headers. +// See https://stackoverflow.com/questions/6012157/is-stdunique-ptrt-required-to-know-the-full-definition-of-t +//**************************************************************************************************************************************************** +AppController::~AppController() = default; + + //**************************************************************************************************************************************************** /// \return The bridge worker, which can be null if the application was run in 'attach' mode (-a command-line switch). //**************************************************************************************************************************************************** @@ -71,6 +81,14 @@ ProcessMonitor *AppController::bridgeMonitor() const { } +//**************************************************************************************************************************************************** +/// \return A reference to the application settings. +//**************************************************************************************************************************************************** +Settings &AppController::settings() { + return *settings_; +} + + //**************************************************************************************************************************************************** /// \param[in] exception The exception that triggered the fatal error. //**************************************************************************************************************************************************** @@ -102,4 +120,4 @@ void AppController::restart(bool isCrashing) { void AppController::setLauncherArgs(const QString &launcher, const QStringList &args) { launcher_ = launcher; launcherArgs_ = args; -} \ No newline at end of file +} diff --git a/internal/frontend/bridge-gui/bridge-gui/AppController.h b/internal/frontend/bridge-gui/bridge-gui/AppController.h index 9821384c..d55e1f11 100644 --- a/internal/frontend/bridge-gui/bridge-gui/AppController.h +++ b/internal/frontend/bridge-gui/bridge-gui/AppController.h @@ -20,8 +20,9 @@ #define BRIDGE_GUI_APP_CONTROLLER_H -// @formatter:off +//@formatter:off class QMLBackend; +class Settings; namespace bridgepp { class Log; class Overseer; @@ -29,7 +30,7 @@ class GRPCClient; class ProcessMonitor; class Exception; } -// @formatter:off +//@formatter:on //**************************************************************************************************************************************************** @@ -42,7 +43,7 @@ Q_OBJECT public: // member functions. AppController(AppController const &) = delete; ///< Disabled copy-constructor. AppController(AppController &&) = delete; ///< Disabled assignment copy-constructor. - ~AppController() override = default; ///< Destructor. + ~AppController() override; ///< 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. @@ -50,7 +51,8 @@ public: // member functions. bridgepp::Log &log() { return *log_; } ///< Return a reference to the log. std::unique_ptr &bridgeOverseer() { return bridgeOverseer_; }; ///< Returns a reference the bridge overseer bridgepp::ProcessMonitor *bridgeMonitor() const; ///< Return the bridge worker. - void setLauncherArgs(const QString& launcher, const QStringList& args); + Settings &settings();; ///< Return the application settings. + void setLauncherArgs(const QString &launcher, const QStringList &args); public slots: void onFatalError(bridgepp::Exception const& e); ///< Handle fatal errors. @@ -64,6 +66,7 @@ private: // data members std::unique_ptr grpc_; ///< The RPC client. std::unique_ptr log_; ///< The log. std::unique_ptr bridgeOverseer_; ///< The overseer for the bridge monitor worker. + std::unique_ptr settings_; ///< The application settings. QString launcher_; QStringList launcherArgs_; }; diff --git a/internal/frontend/bridge-gui/bridge-gui/BuildConfig.h.in b/internal/frontend/bridge-gui/bridge-gui/BuildConfig.h.in index 2ebdf00d..cc05b229 100644 --- a/internal/frontend/bridge-gui/bridge-gui/BuildConfig.h.in +++ b/internal/frontend/bridge-gui/bridge-gui/BuildConfig.h.in @@ -19,12 +19,13 @@ #ifndef BRIDGE_GUI_VERSION_H #define BRIDGE_GUI_VERSION_H -#define PROJECT_FULL_NAME "@BRIDGE_APP_FULL_NAME@" -#define PROJECT_VENDOR "@BRIDGE_VENDOR@" -#define PROJECT_VER "@BRIDGE_APP_VERSION@" -#define PROJECT_REVISION "@BRIDGE_REVISION@" -#define PROJECT_BUILD_TIME "@BRIDGE_BUILD_TIME@" -#define PROJECT_DSN_SENTRY "@BRIDGE_DSN_SENTRY@" -#define PROJECT_BUILD_ENV "@BRIDGE_BUILD_ENV@" +#define PROJECT_FULL_NAME "@BRIDGE_APP_FULL_NAME@" +#define PROJECT_VENDOR "@BRIDGE_VENDOR@" +#define PROJECT_VER "@BRIDGE_APP_VERSION@" +#define PROJECT_REVISION "@BRIDGE_REVISION@" +#define PROJECT_BUILD_TIME "@BRIDGE_BUILD_TIME@" +#define PROJECT_DSN_SENTRY "@BRIDGE_DSN_SENTRY@" +#define PROJECT_BUILD_ENV "@BRIDGE_BUILD_ENV@" +#define PROJECT_CRASHPAD_HANDLER_PATH "@BRIDGE_CRASHPAD_HANDLER_PATH@" #endif // BRIDGE_GUI_VERSION_H diff --git a/internal/frontend/bridge-gui/bridge-gui/CMakeLists.txt b/internal/frontend/bridge-gui/bridge-gui/CMakeLists.txt index 4d4d6d89..e491356f 100644 --- a/internal/frontend/bridge-gui/bridge-gui/CMakeLists.txt +++ b/internal/frontend/bridge-gui/bridge-gui/CMakeLists.txt @@ -85,7 +85,6 @@ message(STATUS "Using Qt ${Qt6_VERSION}") #***************************************************************************************************************************************************** find_package(sentry CONFIG REQUIRED) - #***************************************************************************************************************************************************** # Source files and output #***************************************************************************************************************************************************** @@ -110,24 +109,23 @@ add_executable(bridge-gui Resources.qrc AppController.cpp AppController.h BridgeApp.cpp BridgeApp.h + BuildConfig.h CommandLine.cpp CommandLine.h EventStreamWorker.cpp EventStreamWorker.h LogUtils.cpp LogUtils.h main.cpp Pch.h - BuildConfig.h QMLBackend.cpp QMLBackend.h UserList.cpp UserList.h SentryUtils.cpp SentryUtils.h + Settings.cpp Settings.h ${DOCK_ICON_SRC_FILE} MacOS/DockIcon.h ) - if (APPLE) target_sources(bridge-gui PRIVATE MacOS/SecondInstance.mm MacOS/SecondInstance.h) endif(APPLE) - if (WIN32) # on Windows, we add a (non-Qt) resource file that contains the application icon and version information. string(TIMESTAMP BRIDGE_BUILD_YEAR "%Y") set(REGEX_NUMBER "[0123456789]") # CMake matches does not support \d. diff --git a/internal/frontend/bridge-gui/bridge-gui/LogUtils.cpp b/internal/frontend/bridge-gui/bridge-gui/LogUtils.cpp index de179dfc..4964039b 100644 --- a/internal/frontend/bridge-gui/bridge-gui/LogUtils.cpp +++ b/internal/frontend/bridge-gui/bridge-gui/LogUtils.cpp @@ -17,6 +17,7 @@ #include "LogUtils.h" +#include "BuildConfig.h" #include @@ -24,9 +25,52 @@ using namespace bridgepp; namespace { - qsizetype const logFileTailMaxLength = 25 * 1024; ///< The maximum length of the portion of log returned by tailOfLatestBridgeLog() +qsizetype const logFileTailMaxLength = 25 * 1024; ///< The maximum length of the portion of log returned by tailOfLatestBridgeLog() } + +//**************************************************************************************************************************************************** +/// \return user logs directory used by bridge. +//**************************************************************************************************************************************************** +QString userLogsDir() { + QString const path = QDir(bridgepp::userDataDir()).absoluteFilePath("logs"); + QDir().mkpath(path); + return path; +} + +//**************************************************************************************************************************************************** +/// \return A reference to the log. +//**************************************************************************************************************************************************** +Log &initLog() { + Log &log = app().log(); + log.registerAsQtMessageHandler(); + log.setEchoInConsole(true); + + // remove old gui log files + QDir const logsDir(userLogsDir()); + for (QFileInfo const fileInfo: logsDir.entryInfoList({ "gui_v*.log" }, QDir::Filter::Files)) { // entryInfolist apparently only support wildcards, not regex. + QFile(fileInfo.absoluteFilePath()).remove(); + } + + // create new GUI log file + QString error; + if (!log.startWritingToFile(logsDir.absoluteFilePath(QString("gui_v%1_%2.log").arg(PROJECT_VER).arg(QDateTime::currentSecsSinceEpoch())), &error)) { + log.error(error); + } + + log.info("bridge-gui starting"); + QString const qtCompileTimeVersion = QT_VERSION_STR; + QString const qtRuntimeVersion = qVersion(); + QString msg = QString("Using Qt %1").arg(qtRuntimeVersion); + if (qtRuntimeVersion != qtCompileTimeVersion) { + msg += QString(" (compiled against %1)").arg(qtCompileTimeVersion); + } + log.info(msg); + + return log; +} + + //**************************************************************************************************************************************************** /// \brief Return the path of the latest bridge log. /// \return The path of the latest bridge log file. @@ -58,5 +102,3 @@ QByteArray tailOfLatestBridgeLog() { return file.open(QIODevice::Text | QIODevice::ReadOnly) ? file.readAll().right(logFileTailMaxLength) : QByteArray(); } - - diff --git a/internal/frontend/bridge-gui/bridge-gui/LogUtils.h b/internal/frontend/bridge-gui/bridge-gui/LogUtils.h index 53320380..60e1896d 100644 --- a/internal/frontend/bridge-gui/bridge-gui/LogUtils.h +++ b/internal/frontend/bridge-gui/bridge-gui/LogUtils.h @@ -20,6 +20,10 @@ #define BRIDGE_GUI_LOG_UTILS_H +#include + + +bridgepp::Log &initLog(); ///< Initialize the application log. QByteArray tailOfLatestBridgeLog(); ///< Return the last bytes of the last bridge log. diff --git a/internal/frontend/bridge-gui/bridge-gui/QMLBackend.cpp b/internal/frontend/bridge-gui/bridge-gui/QMLBackend.cpp index 5ba5d2c6..b0cbad7e 100644 --- a/internal/frontend/bridge-gui/bridge-gui/QMLBackend.cpp +++ b/internal/frontend/bridge-gui/bridge-gui/QMLBackend.cpp @@ -17,11 +17,12 @@ #include "QMLBackend.h" -#include "EventStreamWorker.h" #include "BuildConfig.h" +#include "EventStreamWorker.h" #include "LogUtils.h" -#include +#include #include +#include #include #include @@ -58,7 +59,7 @@ void QMLBackend::init(GRPCConfig const &serviceConfig) { app().grpc().setLog(&log); this->connectGrpcEvents(); - app().grpc().connectToServer(serviceConfig, app().bridgeMonitor()); + app().grpc().connectToServer(bridgepp::userConfigDir(), serviceConfig, app().bridgeMonitor()); app().log().info("Connected to backend via gRPC service."); QString bridgeVer; @@ -178,16 +179,6 @@ void QMLBackend::setShowSplashScreen(bool show) { } -//**************************************************************************************************************************************************** -/// \return The value for the 'showSplashScreen' property. -//**************************************************************************************************************************************************** -bool QMLBackend::showSplashScreen() const { - HANDLE_EXCEPTION_RETURN_BOOL( - return showSplashScreen_; - ) -} - - //**************************************************************************************************************************************************** /// \return The value for the 'GOOS' property. //**************************************************************************************************************************************************** @@ -198,6 +189,16 @@ QString QMLBackend::goos() const { } +//**************************************************************************************************************************************************** +/// \return The value for the 'showSplashScreen' property. +//**************************************************************************************************************************************************** +bool QMLBackend::showSplashScreen() const { + HANDLE_EXCEPTION_RETURN_BOOL( + return showSplashScreen_; + ) +} + + //**************************************************************************************************************************************************** /// \return The value for the 'logsPath' property. //**************************************************************************************************************************************************** @@ -465,6 +466,7 @@ bool QMLBackend::isDoHEnabled() const { ) } + //**************************************************************************************************************************************************** /// \return The value for the 'isAutomaticUpdateOn' property. //**************************************************************************************************************************************************** @@ -910,6 +912,24 @@ void QMLBackend::onUserBadEvent(QString const &userID, QString const& ) { } +//**************************************************************************************************************************************************** +/// \param[in] username The username (or primary email address) +//**************************************************************************************************************************************************** +void QMLBackend::onIMAPLoginFailed(QString const &username) { + HANDLE_EXCEPTION( + SPUser const user = users_->getUserWithUsernameOrEmail(username); + if ((!user) || (user->state() != UserState::SignedOut)) { // We want to pop-up only if a signed-out user has been detected + return; + } + if (user->isInIMAPLoginFailureCooldown()) + return; + user->startImapLoginFailureCooldown(60 * 60 * 1000); // 1 hour cooldown during which we will not display this notification to this user again. + emit selectUser(user->id()); + emit imapLoginWhileSignedOut(username); + ) +} + + //**************************************************************************************************************************************************** // //**************************************************************************************************************************************************** @@ -1018,6 +1038,8 @@ void QMLBackend::connectGrpcEvents() { // user events connect(client, &GRPCClient::userDisconnected, this, &QMLBackend::userDisconnected); connect(client, &GRPCClient::userBadEvent, this, &QMLBackend::onUserBadEvent); + connect(client, &GRPCClient::imapLoginFailed, this, &QMLBackend::onIMAPLoginFailed); + users_->connectGRPCEvents(); } diff --git a/internal/frontend/bridge-gui/bridge-gui/QMLBackend.h b/internal/frontend/bridge-gui/bridge-gui/QMLBackend.h index aa0b872a..80754c6f 100644 --- a/internal/frontend/bridge-gui/bridge-gui/QMLBackend.h +++ b/internal/frontend/bridge-gui/bridge-gui/QMLBackend.h @@ -181,6 +181,7 @@ public slots: // slot for signals received from gRPC that need transformation in void onLoginFinished(QString const &userID, bool wasSignedOut); ///< Slot for LoginFinished gRPC event. void onLoginAlreadyLoggedIn(QString const &userID); ///< Slot for the LoginAlreadyLoggedIn gRPC event. void onUserBadEvent(QString const& userID, QString const& errorMessage); ///< Slot for the userBadEvent gRPC event. + void onIMAPLoginFailed(QString const& username); ///< Slot the the imapLoginFailed event. signals: // Signals received from the Go backend, to be forwarded to QML void toggleAutostartFinished(); ///< Signal for the 'toggleAutostartFinished' gRPC stream event. @@ -234,6 +235,7 @@ signals: // Signals received from the Go backend, to be forwarded to QML void hideMainWindow(); ///< Signal for the 'hideMainWindow' gRPC stream event. void genericError(QString const &title, QString const &description); ///< Signal for the 'genericError' gRPC stream event. void selectUser(QString const); ///< Signal that request the given user account to be displayed. + void imapLoginWhileSignedOut(QString const& username); ///< Signal for the notification of IMAP login attempt on a signed out account. // This signal is emitted when an exception is intercepted is calls triggered by QML. QML engine would intercept the exception otherwise. void fatalError(bridgepp::Exception const& e) const; ///< Signal emitted when an fatal error occurs. diff --git a/internal/frontend/bridge-gui/bridge-gui/SentryUtils.cpp b/internal/frontend/bridge-gui/bridge-gui/SentryUtils.cpp index 74aadafc..b7d80720 100644 --- a/internal/frontend/bridge-gui/bridge-gui/SentryUtils.cpp +++ b/internal/frontend/bridge-gui/bridge-gui/SentryUtils.cpp @@ -20,10 +20,8 @@ #include #include -#include -#include -#include -#include + +using namespace bridgepp; static constexpr const char *LoggerName = "bridge-gui"; @@ -46,25 +44,54 @@ QString sentryAttachmentFilePath() { } +//**************************************************************************************************************************************************** +/// \brief Get a hash of the computer's host name +//**************************************************************************************************************************************************** QByteArray getProtectedHostname() { QByteArray hostname = QCryptographicHash::hash(QSysInfo::machineHostName().toUtf8(), QCryptographicHash::Sha256); return hostname.toHex(); } +//**************************************************************************************************************************************************** +/// \return The OS String used by sentry +//**************************************************************************************************************************************************** QString getApiOS() { -#if defined(Q_OS_DARWIN) - return "macos"; -#elif defined(Q_OS_WINDOWS) - return "windows"; -#else - return "linux"; -#endif + switch (os()) { + case OS::MacOS: + return "macos"; + case OS::Windows: + return "windows"; + case OS::Linux: + default: + return "linux"; + } } +//**************************************************************************************************************************************************** +/// \return The application version number. +//**************************************************************************************************************************************************** QString appVersion(const QString& version) { - return QString("%1-bridge@%2").arg(getApiOS()).arg(version); + return QString("%1-bridge@%2").arg(getApiOS(), version); } + +//**************************************************************************************************************************************************** +// +//**************************************************************************************************************************************************** +void initSentry() { + sentry_options_t *sentryOptions = newSentryOptions(PROJECT_DSN_SENTRY, sentryCacheDir().toStdString().c_str()); + if (!QString(PROJECT_CRASHPAD_HANDLER_PATH).isEmpty()) + sentry_options_set_handler_path(sentryOptions, PROJECT_CRASHPAD_HANDLER_PATH); + + if (sentry_init(sentryOptions) != 0) { + QTextStream(stderr) << "Failed to initialize sentry\n"; + } + setSentryReportScope(); +} + +//**************************************************************************************************************************************************** +// +//**************************************************************************************************************************************************** void setSentryReportScope() { sentry_set_tag("OS", bridgepp::goos().toUtf8()); sentry_set_tag("Client", PROJECT_FULL_NAME); @@ -76,6 +103,10 @@ void setSentryReportScope() { sentry_set_user(user); } + +//**************************************************************************************************************************************************** +// +//**************************************************************************************************************************************************** sentry_options_t* newSentryOptions(const char *sentryDNS, const char *cacheDir) { sentry_options_t *sentryOptions = sentry_options_new(); sentry_options_set_dsn(sentryOptions, sentryDNS); @@ -92,12 +123,18 @@ sentry_options_t* newSentryOptions(const char *sentryDNS, const char *cacheDir) } +//**************************************************************************************************************************************************** +// +//**************************************************************************************************************************************************** sentry_uuid_t reportSentryEvent(sentry_level_t level, const char *message) { auto event = sentry_value_new_message_event(level, LoggerName, message); return sentry_capture_event(event); } +//**************************************************************************************************************************************************** +// +//**************************************************************************************************************************************************** sentry_uuid_t reportSentryException(sentry_level_t level, const char *message, const char *exceptionType, const char *exception) { auto event = sentry_value_new_message_event(level, LoggerName, message); sentry_event_add_exception(event, sentry_value_new_exception(exceptionType, exception)); diff --git a/internal/frontend/bridge-gui/bridge-gui/SentryUtils.h b/internal/frontend/bridge-gui/bridge-gui/SentryUtils.h index bb30e34e..8d1d966e 100644 --- a/internal/frontend/bridge-gui/bridge-gui/SentryUtils.h +++ b/internal/frontend/bridge-gui/bridge-gui/SentryUtils.h @@ -21,7 +21,7 @@ #include - +void initSentry(); void setSentryReportScope(); sentry_options_t* newSentryOptions(const char * sentryDNS, const char * cacheDir); sentry_uuid_t reportSentryEvent(sentry_level_t level, const char *message); diff --git a/internal/frontend/bridge-gui/bridge-gui/Settings.cpp b/internal/frontend/bridge-gui/bridge-gui/Settings.cpp new file mode 100644 index 00000000..e7acb259 --- /dev/null +++ b/internal/frontend/bridge-gui/bridge-gui/Settings.cpp @@ -0,0 +1,56 @@ +// Copyright (c) 2023 Proton AG +// +// This file is part of Proton Mail Bridge. +// +// Proton Mail Bridge is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Proton Mail Bridge is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Proton Mail Bridge. If not, see . + + +#include "Settings.h" +#include + + +using namespace bridgepp; + + +namespace { + + +QString const settingsFileName = "bridge-gui.ini"; ///< The name of the settings file. +QString const keyUseSoftwareRenderer = "UseSoftwareRenderer"; ///< The key for storing the 'Use software rendering' setting. + + +} + +//**************************************************************************************************************************************************** +// +//**************************************************************************************************************************************************** +Settings::Settings() + : settings_(QDir(userConfigDir()).absoluteFilePath("bridge-gui.ini"), QSettings::Format::IniFormat) { +} + + +//**************************************************************************************************************************************************** +/// \return The value for the 'Use software renderer' setting. +//**************************************************************************************************************************************************** +bool Settings::useSoftwareRenderer() const { + return settings_.value(keyUseSoftwareRenderer, false).toBool(); +} + + +//**************************************************************************************************************************************************** +/// \param[in] value The value for the 'Use software renderer' setting. +//**************************************************************************************************************************************************** +void Settings::setUseSoftwareRenderer(bool value) { + settings_.setValue(keyUseSoftwareRenderer, value); +} diff --git a/internal/frontend/bridge-gui/bridge-gui/Settings.h b/internal/frontend/bridge-gui/bridge-gui/Settings.h new file mode 100644 index 00000000..977fc6ba --- /dev/null +++ b/internal/frontend/bridge-gui/bridge-gui/Settings.h @@ -0,0 +1,47 @@ +// Copyright (c) 2023 Proton AG +// +// This file is part of Proton Mail Bridge. +// +// Proton Mail Bridge is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Proton Mail Bridge is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Proton Mail Bridge. If not, see . + + +#ifndef BRIDGE_GUI_SETTINGS_H +#define BRIDGE_GUI_SETTINGS_H + + +//**************************************************************************************************************************************************** +/// \brief Application settings class +//**************************************************************************************************************************************************** +class Settings { +public: // member functions. + Settings(Settings const&) = delete; ///< Disabled copy-constructor. + Settings(Settings&&) = delete; ///< Disabled assignment copy-constructor. + ~Settings() = default; ///< Destructor. + Settings& operator=(Settings const&) = delete; ///< Disabled assignment operator. + Settings& operator=(Settings&&) = delete; ///< Disabled move assignment operator. + + bool useSoftwareRenderer() const; ///< Get the 'Use software renderer' settings value. + void setUseSoftwareRenderer(bool value); ///< Set the 'Use software renderer' settings value. + +private: // member functions. + Settings(); ///< Default constructor. + +private: // data members. + QSettings settings_; ///< The settings. + + friend class AppController; +}; + + +#endif //BRIDGE_GUI_SETTINGS_H diff --git a/internal/frontend/bridge-gui/bridge-gui/UserList.cpp b/internal/frontend/bridge-gui/bridge-gui/UserList.cpp index 908dbd1c..059d0b1e 100644 --- a/internal/frontend/bridge-gui/bridge-gui/UserList.cpp +++ b/internal/frontend/bridge-gui/bridge-gui/UserList.cpp @@ -38,6 +38,10 @@ void UserList::connectGRPCEvents() const { GRPCClient &client = app().grpc(); connect(&client, &GRPCClient::userChanged, this, &UserList::onUserChanged); connect(&client, &GRPCClient::toggleSplitModeFinished, this, &UserList::onToggleSplitModeFinished); + connect(&client, &GRPCClient::usedBytesChanged, this, &UserList::onUsedBytesChanged); + connect(&client, &GRPCClient::syncStarted, this, &UserList::onSyncStarted); + connect(&client, &GRPCClient::syncFinished, this, &UserList::onSyncFinished); + connect(&client, &GRPCClient::syncProgress, this, &UserList::onSyncProgress); } @@ -148,6 +152,19 @@ bridgepp::SPUser UserList::getUserWithID(QString const &userID) const { } +//**************************************************************************************************************************************************** +/// \param[in] username The username or email. +/// \return The user with the given ID. +/// \return A null pointer if the user could not be found. +//**************************************************************************************************************************************************** +bridgepp::SPUser UserList::getUserWithUsernameOrEmail(QString const &username) const { + QList::const_iterator it = std::find_if(users_.begin(), users_.end(), [username](SPUser const &user) -> bool { + return user && ((username.compare(user->username(), Qt::CaseInsensitive) == 0) || user->addresses().contains(username, Qt::CaseInsensitive)); + }); + return (it == users_.end()) ? nullptr : *it; +} + + //**************************************************************************************************************************************************** /// \param[in] row The row. //**************************************************************************************************************************************************** @@ -223,3 +240,61 @@ void UserList::onToggleSplitModeFinished(QString const &userID) { int UserList::count() const { return users_.size(); } + + +//**************************************************************************************************************************************************** +/// \param[in] userID The userID. +/// \param[in] usedBytes The used space, in bytes. +//**************************************************************************************************************************************************** +void UserList::onUsedBytesChanged(QString const &userID, qint64 usedBytes) { + int const index = this->rowOfUserID(userID); + if (index < 0) { + app().log().error(QString("Received usedBytesChanged event for unknown userID %1").arg(userID)); + return; + } + users_[index]->setUsedBytes(usedBytes); +} + + +//**************************************************************************************************************************************************** +/// \param[in] userID The userID. +//**************************************************************************************************************************************************** +void UserList::onSyncStarted(QString const &userID) { + int const index = this->rowOfUserID(userID); + if (index < 0) { + app().log().error(QString("Received onSyncStarted event for unknown userID %1").arg(userID)); + return; + } + users_[index]->setIsSyncing(true); +} + + +//**************************************************************************************************************************************************** +/// \param[in] userID The userID. +//**************************************************************************************************************************************************** +void UserList::onSyncFinished(QString const &userID) { + int const index = this->rowOfUserID(userID); + if (index < 0) { + app().log().error(QString("Received onSyncFinished event for unknown userID %1").arg(userID)); + return; + } + users_[index]->setIsSyncing(false); +} + + +//**************************************************************************************************************************************************** +/// \param[in] userID The userID. +/// \param[in] progress The sync progress ratio. +/// \param[in] elapsedMs The elapsed sync time in milliseconds. +/// \param[in] remainingMs The remaining sync time in milliseconds. +//**************************************************************************************************************************************************** +void UserList::onSyncProgress(QString const &userID, double progress, float elapsedMs, float remainingMs) { + Q_UNUSED(elapsedMs) + Q_UNUSED(remainingMs) + int const index = this->rowOfUserID(userID); + if (index < 0) { + app().log().error(QString("Received onSyncFinished event for unknown userID %1").arg(userID)); + return; + } + users_[index]->setSyncProgress(progress); +} diff --git a/internal/frontend/bridge-gui/bridge-gui/UserList.h b/internal/frontend/bridge-gui/bridge-gui/UserList.h index 60d06c44..170b5c04 100644 --- a/internal/frontend/bridge-gui/bridge-gui/UserList.h +++ b/internal/frontend/bridge-gui/bridge-gui/UserList.h @@ -44,6 +44,7 @@ public: // member functions. void appendUser(bridgepp::SPUser const &user); ///< Add a new user. void updateUserAtRow(int row, bridgepp::User const &user); ///< Update the user at given row. bridgepp::SPUser getUserWithID(QString const &userID) const; ///< Retrieve the user with the given ID. + bridgepp::SPUser getUserWithUsernameOrEmail(QString const& username) const; ///< Retrieve the user with the given primary email address or username // the userCount property. Q_PROPERTY(int count READ count NOTIFY countChanged) @@ -59,7 +60,11 @@ public: public slots: ///< handler for signals coming from the gRPC service void onUserChanged(QString const &userID); void onToggleSplitModeFinished(QString const &userID); - + void onUsedBytesChanged(QString const &userID, qint64 usedBytes); ///< Slot for usedBytesChanged events. + void onSyncStarted(QString const &userID); ///< Slot for syncStarted events. + void onSyncFinished(QString const &userID); ///< Slot for syncFinished events. + void onSyncProgress(QString const &userID, double progress, float elapsedMs, float remainingMs); ///< Slot for syncFinished events. + private: // data members QList users_; ///< The user list. }; diff --git a/internal/frontend/bridge-gui/bridge-gui/build.ps1 b/internal/frontend/bridge-gui/bridge-gui/build.ps1 index 004b1a54..1549e272 100644 --- a/internal/frontend/bridge-gui/bridge-gui/build.ps1 +++ b/internal/frontend/bridge-gui/bridge-gui/build.ps1 @@ -92,7 +92,7 @@ git submodule update --init --recursive $vcpkgRoot . $cmakeExe -G "Visual Studio 17 2022" -DCMAKE_BUILD_TYPE="$buildConfig" ` -DBRIDGE_APP_FULL_NAME="$bridgeFullName" ` -DBRIDGE_VENDOR="$bridgeVendor" ` - -DBRIDGE_REVISION=$REVISION_HASH ` + -DBRIDGE_REVISION="$REVISION_HASH" ` -DBRIDGE_APP_VERSION="$bridgeVersion" ` -DBRIDGE_BUILD_TIME="$bridgeBuidTime" ` -DBRIDGE_DSN_SENTRY="$bridgeDsnSentry" ` diff --git a/internal/frontend/bridge-gui/bridge-gui/main.cpp b/internal/frontend/bridge-gui/bridge-gui/main.cpp index 1e6c01e0..3f7a6a14 100644 --- a/internal/frontend/bridge-gui/bridge-gui/main.cpp +++ b/internal/frontend/bridge-gui/bridge-gui/main.cpp @@ -16,20 +16,18 @@ // along with Proton Mail Bridge. If not, see . -#include "Pch.h" #include "BridgeApp.h" +#include "BuildConfig.h" #include "CommandLine.h" +#include "LogUtils.h" #include "QMLBackend.h" #include "SentryUtils.h" -#include "BuildConfig.h" -#include "LogUtils.h" +#include "Settings.h" #include #include #include #include #include -#include -#include #ifdef Q_OS_MACOS @@ -99,38 +97,6 @@ void initQtApplication() { } -//**************************************************************************************************************************************************** -/// \return A reference to the log. -//**************************************************************************************************************************************************** -Log &initLog() { - Log &log = app().log(); - log.registerAsQtMessageHandler(); - log.setEchoInConsole(true); - - // remove old gui log files - QDir const logsDir(userLogsDir()); - for (QFileInfo const fileInfo: logsDir.entryInfoList({ "gui_v*.log" }, QDir::Filter::Files)) { // entryInfolist apparently only support wildcards, not regex. - QFile(fileInfo.absoluteFilePath()).remove(); - } - - // create new GUI log file - QString error; - if (!log.startWritingToFile(logsDir.absoluteFilePath(QString("gui_v%1_%2.log").arg(PROJECT_VER).arg(QDateTime::currentSecsSinceEpoch())), &error)) { - log.error(error); - } - - log.info("bridge-gui starting"); - QString const qtCompileTimeVersion = QT_VERSION_STR; - QString const qtRuntimeVersion = qVersion(); - QString msg = QString("Using Qt %1").arg(qtRuntimeVersion); - if (qtRuntimeVersion != qtCompileTimeVersion) { - msg += QString(" (compiled against %1)").arg(qtCompileTimeVersion); - } - log.info(msg); - - return log; -} - //**************************************************************************************************************************************************** /// \param[in] engine The QML component. @@ -150,8 +116,9 @@ QQmlComponent *createRootQmlComponent(QQmlApplicationEngine &engine) { rootComponent->loadUrl(QUrl(qrcQmlDir + "/Bridge.qml")); if (rootComponent->status() != QQmlComponent::Status::Ready) { - app().log().error(rootComponent->errorString()); - throw Exception("Could not load QML component"); + QString const &err =rootComponent->errorString(); + app().log().error(err); + throw Exception("Could not load QML component", err); } return rootComponent; } @@ -218,7 +185,7 @@ QUrl getApiUrl() { /// \return true if an instance of bridge is already running. //**************************************************************************************************************************************************** bool isBridgeRunning() { - QLockFile lockFile(QDir(userCacheDir()).absoluteFilePath(bridgeLock)); + QLockFile lockFile(QDir(bridgepp::userCacheDir()).absoluteFilePath(bridgeLock)); return (!lockFile.tryLock()) && (lockFile.error() == QLockFile::LockFailedError); } @@ -229,8 +196,21 @@ bool isBridgeRunning() { void focusOtherInstance() { try { FocusGRPCClient client; + GRPCConfig sc; + QString const path = FocusGRPCClient::grpcFocusServerConfigPath(bridgepp::userConfigDir()); + QFile file(path); + if (file.exists()) { + if (!sc.load(path)) { + throw Exception("The gRPC focus service configuration file is invalid."); + } + } + else { + throw Exception("Server did not provide gRPC Focus service configuration."); + } + + QString error; - if (!client.connectToServer(5000, &error)) { + if (!client.connectToServer(5000, sc.port, &error)) { throw Exception(QString("Could not connect to bridge focus service for a raise call: %1").arg(error)); } if (!client.raise().ok()) { @@ -247,6 +227,7 @@ void focusOtherInstance() { //**************************************************************************************************************************************************** /// \param [in] args list of arguments to pass to bridge. +/// \return bridge executable path //**************************************************************************************************************************************************** const QString launchBridge(QStringList const &args) { UPOverseer &overseer = app().bridgeOverseer(); @@ -276,12 +257,8 @@ void closeBridgeApp() { app().grpc().quit(); // this will cause the grpc service and the bridge app to close. UPOverseer &overseer = app().bridgeOverseer(); - if (!overseer) { // The app was run in 'attach' mode and attached to an existing instance of Bridge. We're not monitoring it. - return; - } - - while (!overseer->isFinished()) { - QThread::msleep(20); + if (overseer) { // A null overseer means the app was run in 'attach' mode. We're not monitoring it. + overseer->wait(Overseer::maxTerminationWaitTimeMs); } } @@ -292,24 +269,23 @@ void closeBridgeApp() { /// \return The exit code for the application. //**************************************************************************************************************************************************** int main(int argc, char *argv[]) { - // Init sentry. - sentry_options_t *sentryOptions = newSentryOptions(PROJECT_DSN_SENTRY, sentryCacheDir().toStdString().c_str()); - - if (sentry_init(sentryOptions) != 0) { - std::cerr << "Failed to initialize sentry" << std::endl; - } - setSentryReportScope(); - auto sentryClose = qScopeGuard([] { sentry_close(); }); - // The application instance is needed to display system message boxes. As we may have to do it in the exception handler, // application instance is create outside the try/catch clause. if (QSysInfo::productType() != "windows") { - QCoreApplication::setAttribute(Qt::AA_UseSoftwareOpenGL); + QCoreApplication::setAttribute(Qt::AA_UseSoftwareOpenGL); // must be called before instantiating the BridgeApp } BridgeApp guiApp(argc, argv); try { + QString const& configDir = bridgepp::userConfigDir(); + + // Init sentry. + initSentry(); + + auto sentryClose = qScopeGuard([] { sentry_close(); }); + + initQtApplication(); Log &log = initLog(); @@ -339,14 +315,16 @@ int main(int argc, char *argv[]) { } // before launching bridge, we remove any trailing service config file, because we need to make sure we get a newly generated one. - GRPCClient::removeServiceConfigFile(); + FocusGRPCClient::removeServiceConfigFile(configDir); + GRPCClient::removeServiceConfigFile(configDir); bridgeexec = launchBridge(cliOptions.bridgeArgs); } - log.info(QString("Retrieving gRPC service configuration from '%1'").arg(QDir::toNativeSeparators(grpcServerConfigPath()))); - app().backend().init(GRPCClient::waitAndRetrieveServiceConfig(cliOptions.attach ? 0 : grpcServiceConfigWaitDelayMs, app().bridgeMonitor())); + log.info(QString("Retrieving gRPC service configuration from '%1'").arg(QDir::toNativeSeparators(grpcServerConfigPath(configDir)))); + app().backend().init(GRPCClient::waitAndRetrieveServiceConfig(configDir, cliOptions.attach ? 0 : grpcServiceConfigWaitDelayMs, + app().bridgeMonitor())); if (!cliOptions.attach) { - GRPCClient::removeServiceConfigFile(); + GRPCClient::removeServiceConfigFile(configDir); } // gRPC communication is established. From now on, log events will be sent to bridge via gRPC. bridge will write these to file, @@ -359,7 +337,7 @@ int main(int argc, char *argv[]) { // The following allows to render QML content in software with a 'Rendering Hardware Interface' (OpenGL, Vulkan, Metal, Direct3D...) // Note that it is different from the Qt::AA_UseSoftwareOpenGL attribute we use on some platforms that instruct Qt that we would like // to use a software-only implementation of OpenGL. - QQuickWindow::setSceneGraphBackend(cliOptions.useSoftwareRenderer ? "software" : "rhi"); + QQuickWindow::setSceneGraphBackend((app().settings().useSoftwareRenderer() || cliOptions.useSoftwareRenderer) ? "software" : "rhi"); log.info(QString("Qt Quick renderer: %1").arg(QQuickWindow::sceneGraphBackend())); @@ -412,7 +390,7 @@ int main(int argc, char *argv[]) { QObject::disconnect(connection); app().grpc().stopEventStreamReader(); - if (!app().backend().waitForEventStreamReaderToFinish(5000)) { + if (!app().backend().waitForEventStreamReaderToFinish(Overseer::maxTerminationWaitTimeMs)) { log.warn("Event stream reader took too long to finish."); } diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/AccountDelegate.qml b/internal/frontend/bridge-gui/bridge-gui/qml/AccountDelegate.qml index 9a5648e4..a0671e76 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/AccountDelegate.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/AccountDelegate.qml @@ -29,14 +29,19 @@ Item { property var _spacing: 12 * ProtonStyle.px - property color usedSpaceColor : { + property color progressColor : { if (!root.enabled) return root.colorScheme.text_weak if (root.type == AccountDelegate.SmallView) return root.colorScheme.text_weak - if (root.usedFraction < .50) return root.colorScheme.signal_success - if (root.usedFraction < .75) return root.colorScheme.signal_warning + if (root.user && root.user.isSyncing) return root.colorScheme.text_weak + if (root.progressRatio < .50) return root.colorScheme.signal_success + if (root.progressRatio < .75) return root.colorScheme.signal_warning return root.colorScheme.signal_danger } - property real usedFraction: root.user ? reasonableFraction(root.user.usedBytes, root.user.totalBytes) : 0 + property real progressRatio: { + if (!root.user) + return 0 + return root.user.isSyncing ? root.user.syncProgress : reasonableFraction(root.user.usedBytes, root.user.totalBytes) + } property string totalSpace: root.spaceWithUnits(root.user ? root.reasonableBytes(root.user.totalBytes) : 0) property string usedSpace: root.spaceWithUnits(root.user ? root.reasonableBytes(root.user.usedBytes) : 0) @@ -171,18 +176,21 @@ Item { case EUserState.Locked: return qsTr("Connecting") + dotsTimer.dots case EUserState.Connected: - return root.usedSpace + if (root.user.isSyncing) + return qsTr("Synchronizing (%1%)").arg(Math.floor(root.user.syncProgress * 100)) + dotsTimer.dots + else + return root.usedSpace } } - Timer { // dots animation while connecting. 1 sec cycle, roughly similar to the webmail loading page. + Timer { // dots animation while connecting & syncing. id:dotsTimer property string dots: "" - interval: 250; + interval: 500; repeat: true; - running: (root.user != null) && (root.user.state === EUserState.Locked) + running: (root.user != null) && ((root.user.state === EUserState.Locked) || (root.user.isSyncing)) onTriggered: { - dots = dots + "." + dots += "." if (dots.length > 3) dots = "" } @@ -191,7 +199,7 @@ Item { } } - color: root.usedSpaceColor + color: root.progressColor type: { switch (root.type) { case AccountDelegate.SmallView: return Label.Caption @@ -202,7 +210,7 @@ Item { Label { colorScheme: root.colorScheme - text: root.user && root.user.state == EUserState.Connected ? " / " + root.totalSpace : "" + text: root.user && root.user.state == EUserState.Connected && !root.user.isSyncing ? " / " + root.totalSpace : "" color: root.colorScheme.text_weak type: { switch (root.type) { @@ -213,26 +221,27 @@ Item { } } + Item { implicitHeight: root.type == AccountDelegate.LargeView ? 3 * ProtonStyle.px : 0 } Rectangle { - id: storage_bar + id: progress_bar visible: root.user ? root.type == AccountDelegate.LargeView : false width: 140 * ProtonStyle.px height: 4 * ProtonStyle.px - radius: ProtonStyle.storage_bar_radius + radius: ProtonStyle.progress_bar_radius color: root.colorScheme.border_weak Rectangle { - id: storage_bar_filled - radius: ProtonStyle.storage_bar_radius - color: root.usedSpaceColor - visible: root.user ? parent.visible && (root.user.state == EUserState.Connected) : false + id: progress_bar_filled + radius: ProtonStyle.progress_bar_radius + color: root.progressColor + visible: root.user ? parent.visible && (root.user.state == EUserState.Connected): false anchors { top : parent.top bottom : parent.bottom left : parent.left } - width: Math.min(1,Math.max(0.02,root.usedFraction)) * parent.width + width: Math.min(1,Math.max(0.02,root.progressRatio)) * parent.width } } } diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/ContentWrapper.qml b/internal/frontend/bridge-gui/bridge-gui/qml/ContentWrapper.qml index 93b2e8f7..6dc46d79 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/ContentWrapper.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/ContentWrapper.qml @@ -29,6 +29,8 @@ Item { property var notifications signal showSetupGuide(var user, string address) + signal closeWindow() + signal quitBridge() RowLayout { anchors.fill: parent @@ -107,7 +109,7 @@ Item { Layout.topMargin: 16 Layout.bottomMargin: 9 - Layout.rightMargin: 16 + Layout.rightMargin: 4 horizontalPadding: 0 @@ -115,6 +117,55 @@ Item { onClicked: rightContent.showGeneralSettings() } + + Button { + id: dotMenuButton + Layout.bottomMargin: 9 + Layout.maximumHeight: 36 + Layout.maximumWidth: 36 + Layout.minimumHeight: 36 + Layout.minimumWidth: 36 + Layout.preferredHeight: 36 + Layout.preferredWidth: 36 + Layout.rightMargin: 16 + Layout.topMargin: 16 + colorScheme: leftBar.colorScheme + horizontalPadding: 0 + icon.source: "/qml/icons/ic-three-dots-vertical.svg" + + onClicked: { + dotMenu.open() + } + + Menu { + id: dotMenu + colorScheme: root.colorScheme + modal: true + y: dotMenuButton.Layout.preferredHeight + dotMenuButton.Layout.bottomMargin + + MenuItem { + colorScheme: root.colorScheme + text: qsTr("Close window") + onClicked: { + root.closeWindow() + } + } + MenuItem { + colorScheme: root.colorScheme + text: qsTr("Quit Bridge") + onClicked: { + root.quitBridge() + } + } + + onClosed: { + parent.checked = false + } + onOpened: { + parent.checked = true + } + } + } } Item {implicitHeight:10} diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/MainWindow.qml b/internal/frontend/bridge-gui/bridge-gui/qml/MainWindow.qml index 2abb52b4..491206ad 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/MainWindow.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/MainWindow.qml @@ -142,6 +142,17 @@ ApplicationWindow { onShowSetupGuide: function(user, address) { root.showSetup(user,address) } + + onCloseWindow: { + root.close() + } + + onQuitBridge: { + // If we ever want to add a confirmation dialog before quitting: + //root.notifications.askQuestion("Quit Bridge", "Insert warning message here.", "Quit", "Cancel", Backend.quit, null) + root.close() + Backend.quit() + } } WelcomeGuide { // 1 diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/NotificationPopups.qml b/internal/frontend/bridge-gui/bridge-gui/qml/NotificationPopups.qml index 5c65ddad..bf6d45bb 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/NotificationPopups.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/NotificationPopups.qml @@ -138,4 +138,9 @@ Item { colorScheme: root.colorScheme notification: root.notifications.genericError } + + NotificationDialog { + colorScheme: root.colorScheme + notification: root.notifications.genericQuestion + } } diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/Notifications/Notifications.qml b/internal/frontend/bridge-gui/bridge-gui/qml/Notifications/Notifications.qml index c1145dda..36886073 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/Notifications/Notifications.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/Notifications/Notifications.qml @@ -32,7 +32,7 @@ QtObject { signal askResetBridge() signal askChangeAllMailVisibility(var isVisibleNow) signal askDeleteAccount(var user) - + signal askQuestion(var title, var description, var option1, var option2, var action1, var action2) enum Group { Connection = 1, Update = 2, @@ -81,7 +81,9 @@ QtObject { root.apiCertIssue, root.noActiveKeyForRecipient, root.userBadEvent, - root.genericError + root.imapLoginWhileSignedOut, + root.genericError, + root.genericQuestion, ] // Connection @@ -1143,6 +1145,34 @@ QtObject { } + property Notification imapLoginWhileSignedOut: Notification { + title: qsTr("IMAP Login failed") + brief: title + description: "#PlaceHolderText" + icon: "./icons/ic-exclamation-circle-filled.svg" + type: Notification.NotificationType.Danger + group: Notifications.Group.Connection + + Connections { + target: Backend + function onImapLoginWhileSignedOut(username) { + root.imapLoginWhileSignedOut.description = qsTr("An email client tried to connect to the account %1, but this account is signed " + + "out. Please sign-in to continue.").arg(username) + root.imapLoginWhileSignedOut.active = true + } + } + + action: [ + Action { + text: qsTr("OK") + + onTriggered: { + root.imapLoginWhileSignedOut.active = false + } + } + ] + } + property Notification genericError: Notification { title: "#PlaceholderText#" description: "#PlaceholderText#" @@ -1168,4 +1198,50 @@ QtObject { } ] } + + property Notification genericQuestion: Notification { + title: "" + brief: "" + description: "" + type: Notification.NotificationType.Warning + group: Notifications.Group.Dialogs + property var option1: "" + property var option2: "" + property variant action1: null + property variant action2: null + + Connections { + target: root + function onAskQuestion(title, description, option1, option2, action1, action2) { + root.genericQuestion.title = title + root.genericQuestion.description = description + root.genericQuestion.option1 = option1 + root.genericQuestion.option2 = option2 + root.genericQuestion.action1 = action1 + root.genericQuestion.action2 = action2 + root.genericQuestion.active = true + } + } + + action: [ + Action { + text: root.genericQuestion.option1 + + onTriggered: { + root.genericQuestion.active = false + if (root.genericQuestion.action1) + root.genericQuestion.action1() + } + }, + Action { + text: root.genericQuestion.option2 + + onTriggered: { + root.genericQuestion.active = false + if (root.genericQuestion.action2) + root.genericQuestion.action2() + } + } + ] + } } diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/Proton/MenuItem.qml b/internal/frontend/bridge-gui/bridge-gui/qml/Proton/MenuItem.qml index 7a6c6c1a..7c30d353 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/Proton/MenuItem.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/Proton/MenuItem.qml @@ -26,13 +26,12 @@ T.MenuItem { property ColorScheme colorScheme - implicitWidth: Math.max(implicitBackgroundWidth + leftInset + rightInset, - implicitContentWidth + leftPadding + rightPadding) + width: parent.width // required. Other item overflows to the right of the menu and get clipped. implicitHeight: Math.max(implicitBackgroundHeight + topInset + bottomInset, implicitContentHeight + topPadding + bottomPadding, implicitIndicatorHeight + topPadding + bottomPadding) - padding: 6 + padding: 12 spacing: 6 icon.width: 24 diff --git a/internal/frontend/bridge-gui/bridge-gui/qml/Proton/Style.qml b/internal/frontend/bridge-gui/bridge-gui/qml/Proton/Style.qml index fe759194..4d542822 100644 --- a/internal/frontend/bridge-gui/bridge-gui/qml/Proton/Style.qml +++ b/internal/frontend/bridge-gui/bridge-gui/qml/Proton/Style.qml @@ -362,7 +362,7 @@ QtObject { property real banner_radius : 12 * root.px // px property real dialog_radius : 12 * root.px // px property real card_radius : 12 * root.px // px - property real storage_bar_radius : 3 * root.px // px + property real progress_bar_radius : 3 * root.px // px property real tooltip_radius : 8 * root.px // px property int heading_font_size: 28 diff --git a/internal/frontend/bridge-gui/bridgepp/bridgepp/BridgeUtils.cpp b/internal/frontend/bridge-gui/bridgepp/bridgepp/BridgeUtils.cpp index 3b4b9289..1647e242 100644 --- a/internal/frontend/bridge-gui/bridgepp/bridgepp/BridgeUtils.cpp +++ b/internal/frontend/bridge-gui/bridgepp/bridgepp/BridgeUtils.cpp @@ -156,16 +156,6 @@ QString userDataDir() { } -//**************************************************************************************************************************************************** -/// \return user logs directory used by bridge. -//**************************************************************************************************************************************************** -QString userLogsDir() { - QString const path = QDir(userDataDir()).absoluteFilePath("logs"); - QDir().mkpath(path); - return path; -} - - //**************************************************************************************************************************************************** /// \return sentry cache directory used by bridge. //**************************************************************************************************************************************************** diff --git a/internal/frontend/bridge-gui/bridgepp/bridgepp/BridgeUtils.h b/internal/frontend/bridge-gui/bridgepp/bridgepp/BridgeUtils.h index 5964b986..280a0de9 100644 --- a/internal/frontend/bridge-gui/bridgepp/bridgepp/BridgeUtils.h +++ b/internal/frontend/bridge-gui/bridgepp/bridgepp/BridgeUtils.h @@ -38,7 +38,7 @@ enum class OS { QString userConfigDir(); ///< Get the path of the user configuration folder. QString userCacheDir(); ///< Get the path of the user cache folder. -QString userLogsDir(); ///< Get the path of the user logs folder. +QString userDataDir(); ///< Get the path of the user data folder. QString sentryCacheDir(); ///< Get the path of the sentry cache folder. QString goos(); ///< return the value of Go's GOOS for the current platform ("darwin", "linux" and "windows" are supported). qint64 randN(qint64 n); ///< return a random integer in the half open range [0,n) diff --git a/internal/frontend/bridge-gui/bridgepp/bridgepp/FocusGRPC/FocusGRPCClient.cpp b/internal/frontend/bridge-gui/bridgepp/bridgepp/FocusGRPC/FocusGRPCClient.cpp index ca1e751e..7c33b801 100644 --- a/internal/frontend/bridge-gui/bridgepp/bridgepp/FocusGRPC/FocusGRPCClient.cpp +++ b/internal/frontend/bridge-gui/bridgepp/bridgepp/FocusGRPC/FocusGRPCClient.cpp @@ -29,7 +29,6 @@ namespace { Empty empty; ///< Empty protobuf message, re-used across calls. -qint64 const port = 1042; ///< The port for the focus service. QString const hostname = "127.0.0.1"; ///< The hostname of the focus service. @@ -39,12 +38,43 @@ QString const hostname = "127.0.0.1"; ///< The hostname of the focus service. namespace bridgepp { +//**************************************************************************************************************************************************** +/// \return the gRPC Focus server config file name +//**************************************************************************************************************************************************** +QString grpcFocusServerConfigFilename() { + return "grpcFocusServerConfig.json"; +} + + +//**************************************************************************************************************************************************** +/// \return The absolute path of the focus service config path. +//**************************************************************************************************************************************************** +QString FocusGRPCClient::grpcFocusServerConfigPath(QString const &configDir) { + return QDir(configDir).absoluteFilePath(grpcFocusServerConfigFilename()); +} + + +//**************************************************************************************************************************************************** +// +//**************************************************************************************************************************************************** +void FocusGRPCClient::removeServiceConfigFile(QString const &configDir) { + QString const path = grpcFocusServerConfigPath(configDir); + if (!QFile(path).exists()) { + return; + } + if (!QFile().remove(path)) { + throw Exception("Could not remove gRPC focus service config file."); + } +} + + //**************************************************************************************************************************************************** /// \param[in] timeoutMs The timeout for the connexion. +/// \param[in] port The gRPC server port. /// \param[out] outError if not null and the function returns false. /// \return true iff the connexion was successfully established. //**************************************************************************************************************************************************** -bool FocusGRPCClient::connectToServer(qint64 timeoutMs, QString *outError) { +bool FocusGRPCClient::connectToServer(qint64 timeoutMs, quint16 port, QString *outError) { try { QString const address = QString("%1:%2").arg(hostname).arg(port); channel_ = grpc::CreateChannel(address.toStdString(), grpc::InsecureChannelCredentials()); diff --git a/internal/frontend/bridge-gui/bridgepp/bridgepp/FocusGRPC/FocusGRPCClient.h b/internal/frontend/bridge-gui/bridgepp/bridgepp/FocusGRPC/FocusGRPCClient.h index 6b6bd9e8..01c3ed43 100644 --- a/internal/frontend/bridge-gui/bridgepp/bridgepp/FocusGRPC/FocusGRPCClient.h +++ b/internal/frontend/bridge-gui/bridgepp/bridgepp/FocusGRPC/FocusGRPCClient.h @@ -27,10 +27,14 @@ namespace bridgepp { -//********************************************************************************************************************** +//**************************************************************************************************************************************************** /// \brief Focus GRPC client class -//********************************************************************************************************************** +//**************************************************************************************************************************************************** class FocusGRPCClient { +public: // static member functions + static void removeServiceConfigFile(QString const &configDir); ///< Delete the service config file. + static QString grpcFocusServerConfigPath(QString const &configDir); ///< Return the path of the gRPC Focus server config file. + public: // member functions. FocusGRPCClient() = default; ///< Default constructor. FocusGRPCClient(FocusGRPCClient const &) = delete; ///< Disabled copy-constructor. @@ -38,8 +42,8 @@ public: // member functions. ~FocusGRPCClient() = default; ///< Destructor. FocusGRPCClient &operator=(FocusGRPCClient const &) = delete; ///< Disabled assignment operator. FocusGRPCClient &operator=(FocusGRPCClient &&) = delete; ///< Disabled move assignment operator. - bool connectToServer(qint64 timeoutMs, QString *outError = nullptr); ///< Connect to the focus server + bool connectToServer(qint64 timeoutMs, quint16 port, QString *outError = nullptr); ///< Connect to the focus server grpc::Status raise(); ///< Performs the 'raise' call. grpc::Status version(QString &outVersion); ///< Performs the 'version' call. diff --git a/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/EventFactory.cpp b/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/EventFactory.cpp index 231a5c8e..7457e17e 100644 --- a/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/EventFactory.cpp +++ b/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/EventFactory.cpp @@ -574,6 +574,78 @@ SPStreamEvent newUserBadEvent(QString const &userID, QString const &errorMessage } +//**************************************************************************************************************************************************** +/// \param[in] userID The userID. +/// \param[in] usedBytes The number of used bytes. +//**************************************************************************************************************************************************** +SPStreamEvent newUsedBytesChangedEvent(QString const &userID, qint64 usedBytes) { + auto event = new grpc::UsedBytesChangedEvent; + event->set_userid(userID.toStdString()); + event->set_usedbytes(usedBytes); + auto userEvent = new grpc::UserEvent; + userEvent->set_allocated_usedbyteschangedevent(event); + return wrapUserEvent(userEvent); +} + + +//**************************************************************************************************************************************************** +/// \param[in] username The username that was provided for the failed IMAP login attempt. +/// \return The event. +//**************************************************************************************************************************************************** +SPStreamEvent newIMAPLoginFailedEvent(QString const &username) { + auto event = new grpc::ImapLoginFailedEvent; + event->set_username(username.toStdString()); + auto userEvent = new grpc::UserEvent; + userEvent->set_allocated_imaploginfailedevent(event); + return wrapUserEvent(userEvent); +} + + +//**************************************************************************************************************************************************** +/// \param[in] userID The userID. +/// \return The event. +//**************************************************************************************************************************************************** +SPStreamEvent newSyncStartedEvent(QString const &userID) { + auto event = new grpc::SyncStartedEvent; + event->set_userid(userID.toStdString()); + auto userEvent = new grpc::UserEvent; + userEvent->set_allocated_syncstartedevent(event); + return wrapUserEvent(userEvent); +} + + +//**************************************************************************************************************************************************** +/// \param[in] userID The userID. +/// \return The event. +//**************************************************************************************************************************************************** +SPStreamEvent newSyncFinishedEvent(QString const &userID) { + auto event = new grpc::SyncFinishedEvent; + event->set_userid(userID.toStdString()); + auto userEvent = new grpc::UserEvent; + userEvent->set_allocated_syncfinishedevent(event); + return wrapUserEvent(userEvent); +} + + +//**************************************************************************************************************************************************** +/// \param[in] userID The userID. +/// \param[in] progress The progress ratio. +/// \param[in] elapsedMs The elapsed time in milliseconds. +/// \param[in] remainingMs The remaining time in milliseconds. +/// \return The event. +//**************************************************************************************************************************************************** +SPStreamEvent newSyncProgressEvent(QString const &userID, double progress, qint64 elapsedMs, qint64 remainingMs) { + auto event = new grpc::SyncProgressEvent; + event->set_userid(userID.toStdString()); + event->set_progress(progress); + event->set_elapsedms(elapsedMs); + event->set_remainingms(remainingMs); + auto userEvent = new grpc::UserEvent; + userEvent->set_allocated_syncprogressevent(event); + return wrapUserEvent(userEvent); +} + + //**************************************************************************************************************************************************** /// \param[in] errorCode The error errorCode. /// \return The event. diff --git a/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/EventFactory.h b/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/EventFactory.h index ed35e712..44a6deae 100644 --- a/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/EventFactory.h +++ b/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/EventFactory.h @@ -78,6 +78,11 @@ SPStreamEvent newToggleSplitModeFinishedEvent(QString const &userID); ///< Creat SPStreamEvent newUserDisconnectedEvent(QString const &username); ///< Create a new UserDisconnectedEvent event. SPStreamEvent newUserChangedEvent(QString const &userID); ///< Create a new UserChangedEvent event. SPStreamEvent newUserBadEvent(QString const &userID, QString const& errorMessage); ///< Create a new UserBadEvent event. +SPStreamEvent newUsedBytesChangedEvent(QString const &userID, qint64 usedBytes); ///< Create a new UsedBytesChangedEvent event. +SPStreamEvent newIMAPLoginFailedEvent(QString const &username); ///< Create a new ImapLoginFailedEvent event. +SPStreamEvent newSyncStartedEvent(QString const &userID); ///< Create a new SyncStarted event. +SPStreamEvent newSyncFinishedEvent(QString const &userID); ///< Create a new SyncFinished event. +SPStreamEvent newSyncProgressEvent(QString const &userID, double progress, qint64 elapsedMs, qint64 remainingMs); ///< Create a new SyncFinished event. // Generic error event SPStreamEvent newGenericErrorEvent(grpc::ErrorCode errorCode); ///< Create a new GenericErrrorEvent event. diff --git a/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/GRPCClient.cpp b/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/GRPCClient.cpp index ff7df494..22b4a376 100644 --- a/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/GRPCClient.cpp +++ b/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/GRPCClient.cpp @@ -45,8 +45,8 @@ qint64 const grpcConnectionRetryDelayMs = 10000; ///< Retry delay for the gRPC c //**************************************************************************************************************************************************** // //**************************************************************************************************************************************************** -void GRPCClient::removeServiceConfigFile() { - QString const path = grpcServerConfigPath(); +void GRPCClient::removeServiceConfigFile(QString const &configDir) { + QString const path = grpcServerConfigPath(configDir); if (!QFile(path).exists()) { return; } @@ -61,8 +61,8 @@ void GRPCClient::removeServiceConfigFile() { /// \param[in] serverProcess An optional server process to monitor. If the process it, no need and retry, as connexion cannot be established. Ignored if null. /// \return The service config. //**************************************************************************************************************************************************** -GRPCConfig GRPCClient::waitAndRetrieveServiceConfig(qint64 timeoutMs, ProcessMonitor *serverProcess) { - QString const path = grpcServerConfigPath(); +GRPCConfig GRPCClient::waitAndRetrieveServiceConfig(QString const &configDir, qint64 timeoutMs, ProcessMonitor *serverProcess) { + QString const path = grpcServerConfigPath(configDir); QFile file(path); QElapsedTimer timer; @@ -109,7 +109,7 @@ void GRPCClient::setLog(Log *log) { /// \param[in] serverProcess An optional server process to monitor. If the process it, no need and retry, as connexion cannot be established. Ignored if null. /// \return true iff the connection was successful. //**************************************************************************************************************************************************** -void GRPCClient::connectToServer(GRPCConfig const &config, ProcessMonitor *serverProcess) { +void GRPCClient::connectToServer(QString const &configDir, GRPCConfig const &config, ProcessMonitor *serverProcess) { try { serverToken_ = config.token.toStdString(); QString address; @@ -147,8 +147,9 @@ void GRPCClient::connectToServer(GRPCConfig const &config, ProcessMonitor *serve break; } // connection established. - if (QDateTime::currentDateTime() > giveUpTime) + if (QDateTime::currentDateTime() > giveUpTime) { throw Exception("Connection to the RPC server failed."); + } } if (channel_->GetState(true) != GRPC_CHANNEL_READY) { @@ -159,7 +160,7 @@ void GRPCClient::connectToServer(GRPCConfig const &config, ProcessMonitor *serve QString const clientToken = QUuid::createUuid().toString(); QString error; - QString clientConfigPath = createClientConfigFile(clientToken, &error); + QString clientConfigPath = createClientConfigFile(configDir, clientToken, &error); if (clientConfigPath.isEmpty()) { throw Exception("gRPC client config could not be saved.", error); } @@ -184,6 +185,14 @@ void GRPCClient::connectToServer(GRPCConfig const &config, ProcessMonitor *serve } +//**************************************************************************************************************************************************** +/// \return true if the gRPC client is connected to the server. +//**************************************************************************************************************************************************** +bool GRPCClient::isConnected() const { + return stub_.get(); +} + + //**************************************************************************************************************************************************** /// \param[in] clientConfigPath The path to the gRPC client config path.- /// \param[in] serverToken The token obtained from the server config file. @@ -223,8 +232,9 @@ grpc::Status GRPCClient::addLogEntry(Log::Level level, QString const &package, Q grpc::Status GRPCClient::guiReady(bool &outShowSplashScreen) { GuiReadyResponse response; Status status = this->logGRPCCallStatus(stub_->GuiReady(this->clientContext().get(), empty, &response), __FUNCTION__); - if (status.ok()) + if (status.ok()) { outShowSplashScreen = response.showsplashscreen(); + } return status; } @@ -395,6 +405,8 @@ grpc::Status GRPCClient::setIsDoHEnabled(bool enabled) { //**************************************************************************************************************************************************** grpc::Status GRPCClient::quit() { // quitting will shut down the gRPC service, to we may get an 'Unavailable' response for the call + if (!this->isConnected()) + return Status::OK; // We're not even connected, we return OK. This maybe be an attempt to do 'a proper' shutdown after an unrecoverable error. return this->logGRPCCallStatus(stub_->Quit(this->clientContext().get(), empty, &empty), __FUNCTION__, { StatusCode::UNAVAILABLE }); } @@ -458,15 +470,6 @@ grpc::Status GRPCClient::showOnStartup(bool &outValue) { } -//**************************************************************************************************************************************************** -/// \param[out] outGoos The value for the property. -/// \return The status for the gRPC call. -//**************************************************************************************************************************************************** -grpc::Status GRPCClient::goos(QString &outGoos) { - return this->logGRPCCallStatus(this->getString(&Bridge::Stub::GoOs, outGoos), __FUNCTION__); -} - - //**************************************************************************************************************************************************** /// \param[out] outPath The value for the property. /// \return The status for the gRPC call. @@ -476,6 +479,15 @@ grpc::Status GRPCClient::logsPath(QUrl &outPath) { } +//**************************************************************************************************************************************************** +/// \param[out] outGoos The value for the property. +/// \return The status for the gRPC call. +//**************************************************************************************************************************************************** +grpc::Status GRPCClient::goos(QString &outGoos) { + return this->logGRPCCallStatus(this->getString(&Bridge::Stub::GoOs, outGoos), __FUNCTION__); +} + + //**************************************************************************************************************************************************** /// \param[out] outPath The value for the property. /// \return The status for the gRPC call. @@ -1377,13 +1389,50 @@ void GRPCClient::processUserEvent(UserEvent const &event) { break; } case UserEvent::kUserBadEvent: { - UserBadEvent const& e = event.userbadevent(); + UserBadEvent const &e = event.userbadevent(); QString const userID = QString::fromStdString(e.userid()); QString const errorMessage = QString::fromStdString(e.errormessage()); this->logTrace(QString("User event received: UserBadEvent (userID = %1, errorMessage = %2).").arg(userID, errorMessage)); emit userBadEvent(userID, errorMessage); break; } + case UserEvent::kUsedBytesChangedEvent: { + UsedBytesChangedEvent const &e = event.usedbyteschangedevent(); + QString const userID = QString::fromStdString(e.userid()); + qint64 const usedBytes = e.usedbytes(); + this->logTrace(QString("User event received: UsedBytesChangedEvent (userID = %1, usedBytes = %2).").arg(userID).arg(usedBytes)); + emit usedBytesChanged(userID, usedBytes); + break; + } + case UserEvent::kImapLoginFailedEvent: { + ImapLoginFailedEvent const &e = event.imaploginfailedevent(); + QString const username = QString::fromStdString(e.username()); + this->logTrace(QString("User event received: IMAPLoginFailed (username = %1).:").arg(username)); + emit imapLoginFailed(username); + break; + } + case UserEvent::kSyncStartedEvent: { + SyncStartedEvent const &e = event.syncstartedevent(); + QString const &userID = QString::fromStdString(e.userid()); + this->logTrace(QString("User event received: SyncStarted (userID = %1).:").arg(userID)); + emit syncStarted(userID); + break; + } + case UserEvent::kSyncFinishedEvent: { + SyncFinishedEvent const &e = event.syncfinishedevent(); + QString const &userID = QString::fromStdString(e.userid()); + this->logTrace(QString("User event received: SyncFinished (userID = %1).:").arg(userID)); + emit syncFinished(userID); + break; + } + case UserEvent::kSyncProgressEvent: { + SyncProgressEvent const &e = event.syncprogressevent(); + QString const &userID = QString::fromStdString(e.userid()); + this->logTrace(QString("User event received SyncProgress (userID = %1, progress = %2, elapsedMs = %3, remainingMs = %4).").arg(userID) + .arg(e.progress()).arg(e.elapsedms()).arg(e.remainingms())); + emit syncProgress(userID, e.progress(), e.elapsedms(), e.remainingms()); + break; + } default: this->logError("Unknown User event received."); } diff --git a/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/GRPCClient.h b/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/GRPCClient.h index e16451e6..6a8a1a21 100644 --- a/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/GRPCClient.h +++ b/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/GRPCClient.h @@ -48,8 +48,8 @@ typedef std::unique_ptr UPClientContext; class GRPCClient : public QObject { Q_OBJECT public: // static member functions - static void removeServiceConfigFile(); ///< Delete the service config file. - static GRPCConfig waitAndRetrieveServiceConfig(qint64 timeoutMs, class ProcessMonitor *serverProcess); ///< Wait and retrieve the service configuration. + static void removeServiceConfigFile(QString const &configDir); ///< Delete the service config file. + static GRPCConfig waitAndRetrieveServiceConfig(QString const &configDir, qint64 timeoutMs, class ProcessMonitor *serverProcess); ///< Wait and retrieve the service configuration. public: // member functions. GRPCClient() = default; ///< Default constructor. @@ -59,7 +59,8 @@ public: // member functions. GRPCClient &operator=(GRPCClient const &) = delete; ///< Disabled assignment operator. GRPCClient &operator=(GRPCClient &&) = delete; ///< Disabled move assignment operator. void setLog(Log *log); ///< Set the log for the client. - void connectToServer(GRPCConfig const &config, class ProcessMonitor *serverProcess); ///< Establish connection to the gRPC server. + void connectToServer(QString const &configDir, GRPCConfig const &config, class ProcessMonitor *serverProcess); ///< Establish connection to the gRPC server. + bool isConnected() const; ///< Check whether the gRPC client is connected to the server. grpc::Status checkTokens(QString const &clientConfigPath, QString &outReturnedClientToken); ///< Performs a token check. grpc::Status addLogEntry(Log::Level level, QString const &package, QString const &message); ///< Performs the "AddLogEntry" gRPC call. @@ -180,6 +181,11 @@ signals: void userDisconnected(QString const &username); void userChanged(QString const &userID); void userBadEvent(QString const &userID, QString const& errorMessage); + void usedBytesChanged(QString const &userID, qint64 usedBytes); + void imapLoginFailed(QString const& username); + void syncStarted(QString const &userID); + void syncFinished(QString const &userID); + void syncProgress(QString const &userID, double progress, qint64 elapsedMs, qint64 remainingMs); public: // keychain related calls grpc::Status availableKeychains(QStringList &outKeychains); diff --git a/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/GRPCUtils.cpp b/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/GRPCUtils.cpp index b651ae54..f04cb63a 100644 --- a/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/GRPCUtils.cpp +++ b/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/GRPCUtils.cpp @@ -59,18 +59,19 @@ bool useFileSocketForGRPC() { //**************************************************************************************************************************************************** +/// \param[in] configDir The folder containing the configuration files. /// \return The absolute path of the service config path. //**************************************************************************************************************************************************** -QString grpcServerConfigPath() { - return QDir(userConfigDir()).absoluteFilePath(grpcServerConfigFilename()); +QString grpcServerConfigPath(QString const &configDir) { + return QDir(configDir).absoluteFilePath(grpcServerConfigFilename()); } //**************************************************************************************************************************************************** /// \return The absolute path of the service config path. //**************************************************************************************************************************************************** -QString grpcClientConfigBasePath() { - return QDir(userConfigDir()).absoluteFilePath(grpcClientConfigBaseFilename()); +QString grpcClientConfigBasePath(QString const &configDir) { + return QDir(configDir).absoluteFilePath(grpcClientConfigBaseFilename()); } @@ -81,8 +82,8 @@ QString grpcClientConfigBasePath() { /// \return The path of the created file. /// \return A null string if the file could not be saved. //**************************************************************************************************************************************************** -QString createClientConfigFile(QString const &token, QString *outError) { - QString const basePath = grpcClientConfigBasePath(); +QString createClientConfigFile(QString const &configDir, QString const &token, QString *outError) { + QString const basePath = grpcClientConfigBasePath(configDir); QString path, error; for (qint32 i = 0; i < 1000; ++i) // we try a decent amount of times { @@ -255,4 +256,4 @@ QString getAvailableFileSocketPath() { } -} // namespace bridgepp \ No newline at end of file +} // namespace bridgepp diff --git a/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/GRPCUtils.h b/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/GRPCUtils.h index 844ec4c5..e749df3b 100644 --- a/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/GRPCUtils.h +++ b/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/GRPCUtils.h @@ -34,9 +34,9 @@ extern std::string const grpcMetadataServerTokenKey; ///< The key for the server typedef std::shared_ptr SPStreamEvent; ///< Type definition for shared pointer to grpc::StreamEvent. -QString grpcServerConfigPath(); ///< Return the path of the gRPC server config file. -QString grpcClientConfigBasePath(); ///< Return the path of the gRPC client config file. -QString createClientConfigFile(QString const &token, QString *outError); ///< Create the client config file the server will retrieve and return its path. +QString grpcServerConfigPath(QString const &configDir); ///< Return the path of the gRPC server config file. +QString grpcClientConfigBasePath(QString const &configDir); ///< Return the path of the gRPC client config file. +QString createClientConfigFile(QString const &configDir, QString const &token, QString *outError); ///< Create the client config file the server will retrieve and return its path. grpc::LogLevel logLevelToGRPC(Log::Level level); ///< Convert a Log::Level to gRPC enum value. Log::Level logLevelFromGRPC(grpc::LogLevel level); ///< Convert a grpc::LogLevel to a Log::Level. grpc::UserState userStateToGRPC(UserState state); ///< Convert a bridgepp::UserState to a grpc::UserState. diff --git a/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/bridge.pb.cc b/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/bridge.pb.cc index b54d50fb..eabd0aae 100644 --- a/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/bridge.pb.cc +++ b/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/bridge.pb.cc @@ -773,6 +773,75 @@ struct UserBadEventDefaultTypeInternal { }; }; PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 UserBadEventDefaultTypeInternal _UserBadEvent_default_instance_; +PROTOBUF_CONSTEXPR UsedBytesChangedEvent::UsedBytesChangedEvent( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_.userid_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.usedbytes_)*/int64_t{0} + , /*decltype(_impl_._cached_size_)*/{}} {} +struct UsedBytesChangedEventDefaultTypeInternal { + PROTOBUF_CONSTEXPR UsedBytesChangedEventDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~UsedBytesChangedEventDefaultTypeInternal() {} + union { + UsedBytesChangedEvent _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 UsedBytesChangedEventDefaultTypeInternal _UsedBytesChangedEvent_default_instance_; +PROTOBUF_CONSTEXPR ImapLoginFailedEvent::ImapLoginFailedEvent( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_.username_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_._cached_size_)*/{}} {} +struct ImapLoginFailedEventDefaultTypeInternal { + PROTOBUF_CONSTEXPR ImapLoginFailedEventDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~ImapLoginFailedEventDefaultTypeInternal() {} + union { + ImapLoginFailedEvent _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 ImapLoginFailedEventDefaultTypeInternal _ImapLoginFailedEvent_default_instance_; +PROTOBUF_CONSTEXPR SyncStartedEvent::SyncStartedEvent( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_.userid_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_._cached_size_)*/{}} {} +struct SyncStartedEventDefaultTypeInternal { + PROTOBUF_CONSTEXPR SyncStartedEventDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~SyncStartedEventDefaultTypeInternal() {} + union { + SyncStartedEvent _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 SyncStartedEventDefaultTypeInternal _SyncStartedEvent_default_instance_; +PROTOBUF_CONSTEXPR SyncFinishedEvent::SyncFinishedEvent( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_.userid_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_._cached_size_)*/{}} {} +struct SyncFinishedEventDefaultTypeInternal { + PROTOBUF_CONSTEXPR SyncFinishedEventDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~SyncFinishedEventDefaultTypeInternal() {} + union { + SyncFinishedEvent _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 SyncFinishedEventDefaultTypeInternal _SyncFinishedEvent_default_instance_; +PROTOBUF_CONSTEXPR SyncProgressEvent::SyncProgressEvent( + ::_pbi::ConstantInitialized): _impl_{ + /*decltype(_impl_.userid_)*/{&::_pbi::fixed_address_empty_string, ::_pbi::ConstantInitialized{}} + , /*decltype(_impl_.progress_)*/0 + , /*decltype(_impl_.elapsedms_)*/int64_t{0} + , /*decltype(_impl_.remainingms_)*/int64_t{0} + , /*decltype(_impl_._cached_size_)*/{}} {} +struct SyncProgressEventDefaultTypeInternal { + PROTOBUF_CONSTEXPR SyncProgressEventDefaultTypeInternal() + : _instance(::_pbi::ConstantInitialized{}) {} + ~SyncProgressEventDefaultTypeInternal() {} + union { + SyncProgressEvent _instance; + }; +}; +PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 SyncProgressEventDefaultTypeInternal _SyncProgressEvent_default_instance_; PROTOBUF_CONSTEXPR GenericErrorEvent::GenericErrorEvent( ::_pbi::ConstantInitialized): _impl_{ /*decltype(_impl_.code_)*/0 @@ -787,7 +856,7 @@ struct GenericErrorEventDefaultTypeInternal { }; PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 GenericErrorEventDefaultTypeInternal _GenericErrorEvent_default_instance_; } // namespace grpc -static ::_pb::Metadata file_level_metadata_bridge_2eproto[59]; +static ::_pb::Metadata file_level_metadata_bridge_2eproto[64]; static const ::_pb::EnumDescriptor* file_level_enum_descriptors_bridge_2eproto[7]; static constexpr ::_pb::ServiceDescriptor const** file_level_service_descriptors_bridge_2eproto = nullptr; @@ -1221,6 +1290,11 @@ const uint32_t TableStruct_bridge_2eproto::offsets[] PROTOBUF_SECTION_VARIABLE(p ::_pbi::kInvalidFieldOffsetTag, ::_pbi::kInvalidFieldOffsetTag, ::_pbi::kInvalidFieldOffsetTag, + ::_pbi::kInvalidFieldOffsetTag, + ::_pbi::kInvalidFieldOffsetTag, + ::_pbi::kInvalidFieldOffsetTag, + ::_pbi::kInvalidFieldOffsetTag, + ::_pbi::kInvalidFieldOffsetTag, PROTOBUF_FIELD_OFFSET(::grpc::UserEvent, _impl_.event_), ~0u, // no _has_bits_ PROTOBUF_FIELD_OFFSET(::grpc::ToggleSplitModeFinishedEvent, _internal_metadata_), @@ -1252,6 +1326,45 @@ const uint32_t TableStruct_bridge_2eproto::offsets[] PROTOBUF_SECTION_VARIABLE(p PROTOBUF_FIELD_OFFSET(::grpc::UserBadEvent, _impl_.userid_), PROTOBUF_FIELD_OFFSET(::grpc::UserBadEvent, _impl_.errormessage_), ~0u, // no _has_bits_ + PROTOBUF_FIELD_OFFSET(::grpc::UsedBytesChangedEvent, _internal_metadata_), + ~0u, // no _extensions_ + ~0u, // no _oneof_case_ + ~0u, // no _weak_field_map_ + ~0u, // no _inlined_string_donated_ + PROTOBUF_FIELD_OFFSET(::grpc::UsedBytesChangedEvent, _impl_.userid_), + PROTOBUF_FIELD_OFFSET(::grpc::UsedBytesChangedEvent, _impl_.usedbytes_), + ~0u, // no _has_bits_ + PROTOBUF_FIELD_OFFSET(::grpc::ImapLoginFailedEvent, _internal_metadata_), + ~0u, // no _extensions_ + ~0u, // no _oneof_case_ + ~0u, // no _weak_field_map_ + ~0u, // no _inlined_string_donated_ + PROTOBUF_FIELD_OFFSET(::grpc::ImapLoginFailedEvent, _impl_.username_), + ~0u, // no _has_bits_ + PROTOBUF_FIELD_OFFSET(::grpc::SyncStartedEvent, _internal_metadata_), + ~0u, // no _extensions_ + ~0u, // no _oneof_case_ + ~0u, // no _weak_field_map_ + ~0u, // no _inlined_string_donated_ + PROTOBUF_FIELD_OFFSET(::grpc::SyncStartedEvent, _impl_.userid_), + ~0u, // no _has_bits_ + PROTOBUF_FIELD_OFFSET(::grpc::SyncFinishedEvent, _internal_metadata_), + ~0u, // no _extensions_ + ~0u, // no _oneof_case_ + ~0u, // no _weak_field_map_ + ~0u, // no _inlined_string_donated_ + PROTOBUF_FIELD_OFFSET(::grpc::SyncFinishedEvent, _impl_.userid_), + ~0u, // no _has_bits_ + PROTOBUF_FIELD_OFFSET(::grpc::SyncProgressEvent, _internal_metadata_), + ~0u, // no _extensions_ + ~0u, // no _oneof_case_ + ~0u, // no _weak_field_map_ + ~0u, // no _inlined_string_donated_ + PROTOBUF_FIELD_OFFSET(::grpc::SyncProgressEvent, _impl_.userid_), + PROTOBUF_FIELD_OFFSET(::grpc::SyncProgressEvent, _impl_.progress_), + PROTOBUF_FIELD_OFFSET(::grpc::SyncProgressEvent, _impl_.elapsedms_), + PROTOBUF_FIELD_OFFSET(::grpc::SyncProgressEvent, _impl_.remainingms_), + ~0u, // no _has_bits_ PROTOBUF_FIELD_OFFSET(::grpc::GenericErrorEvent, _internal_metadata_), ~0u, // no _extensions_ ~0u, // no _oneof_case_ @@ -1314,11 +1427,16 @@ static const ::_pbi::MigrationSchema schemas[] PROTOBUF_SECTION_VARIABLE(protode { 406, -1, -1, sizeof(::grpc::AddressChangedLogoutEvent)}, { 413, -1, -1, sizeof(::grpc::ApiCertIssueEvent)}, { 419, -1, -1, sizeof(::grpc::UserEvent)}, - { 430, -1, -1, sizeof(::grpc::ToggleSplitModeFinishedEvent)}, - { 437, -1, -1, sizeof(::grpc::UserDisconnectedEvent)}, - { 444, -1, -1, sizeof(::grpc::UserChangedEvent)}, - { 451, -1, -1, sizeof(::grpc::UserBadEvent)}, - { 459, -1, -1, sizeof(::grpc::GenericErrorEvent)}, + { 435, -1, -1, sizeof(::grpc::ToggleSplitModeFinishedEvent)}, + { 442, -1, -1, sizeof(::grpc::UserDisconnectedEvent)}, + { 449, -1, -1, sizeof(::grpc::UserChangedEvent)}, + { 456, -1, -1, sizeof(::grpc::UserBadEvent)}, + { 464, -1, -1, sizeof(::grpc::UsedBytesChangedEvent)}, + { 472, -1, -1, sizeof(::grpc::ImapLoginFailedEvent)}, + { 479, -1, -1, sizeof(::grpc::SyncStartedEvent)}, + { 486, -1, -1, sizeof(::grpc::SyncFinishedEvent)}, + { 493, -1, -1, sizeof(::grpc::SyncProgressEvent)}, + { 503, -1, -1, sizeof(::grpc::GenericErrorEvent)}, }; static const ::_pb::Message* const file_default_instances[] = { @@ -1380,6 +1498,11 @@ static const ::_pb::Message* const file_default_instances[] = { &::grpc::_UserDisconnectedEvent_default_instance_._instance, &::grpc::_UserChangedEvent_default_instance_._instance, &::grpc::_UserBadEvent_default_instance_._instance, + &::grpc::_UsedBytesChangedEvent_default_instance_._instance, + &::grpc::_ImapLoginFailedEvent_default_instance_._instance, + &::grpc::_SyncStartedEvent_default_instance_._instance, + &::grpc::_SyncFinishedEvent_default_instance_._instance, + &::grpc::_SyncProgressEvent_default_instance_._instance, &::grpc::_GenericErrorEvent_default_instance_._instance, }; @@ -1503,137 +1626,151 @@ const char descriptor_table_protodef_bridge_2eproto[] PROTOBUF_SECTION_VARIABLE( "Event\022\r\n\005email\030\001 \001(\t\"&\n\023AddressChangedEv" "ent\022\017\n\007address\030\001 \001(\t\",\n\031AddressChangedLo" "goutEvent\022\017\n\007address\030\001 \001(\t\"\023\n\021ApiCertIss" - "ueEvent\"\357\001\n\tUserEvent\022E\n\027toggleSplitMode" + "ueEvent\"\211\004\n\tUserEvent\022E\n\027toggleSplitMode" "Finished\030\001 \001(\0132\".grpc.ToggleSplitModeFin" "ishedEventH\000\0227\n\020userDisconnected\030\002 \001(\0132\033" ".grpc.UserDisconnectedEventH\000\022-\n\013userCha" "nged\030\003 \001(\0132\026.grpc.UserChangedEventH\000\022*\n\014" "userBadEvent\030\004 \001(\0132\022.grpc.UserBadEventH\000" - "B\007\n\005event\".\n\034ToggleSplitModeFinishedEven" - "t\022\016\n\006userID\030\001 \001(\t\")\n\025UserDisconnectedEve" - "nt\022\020\n\010username\030\001 \001(\t\"\"\n\020UserChangedEvent" - "\022\016\n\006userID\030\001 \001(\t\"4\n\014UserBadEvent\022\016\n\006user" - "ID\030\001 \001(\t\022\024\n\014errorMessage\030\002 \001(\t\"2\n\021Generi" - "cErrorEvent\022\035\n\004code\030\001 \001(\0162\017.grpc.ErrorCo" - "de*q\n\010LogLevel\022\r\n\tLOG_PANIC\020\000\022\r\n\tLOG_FAT" - "AL\020\001\022\r\n\tLOG_ERROR\020\002\022\014\n\010LOG_WARN\020\003\022\014\n\010LOG" - "_INFO\020\004\022\r\n\tLOG_DEBUG\020\005\022\r\n\tLOG_TRACE\020\006*6\n" - "\tUserState\022\016\n\nSIGNED_OUT\020\000\022\n\n\006LOCKED\020\001\022\r" - "\n\tCONNECTED\020\002*\242\001\n\016LoginErrorType\022\033\n\027USER" - "NAME_PASSWORD_ERROR\020\000\022\r\n\tFREE_USER\020\001\022\024\n\020" - "CONNECTION_ERROR\020\002\022\r\n\tTFA_ERROR\020\003\022\r\n\tTFA" - "_ABORT\020\004\022\027\n\023TWO_PASSWORDS_ERROR\020\005\022\027\n\023TWO" - "_PASSWORDS_ABORT\020\006*[\n\017UpdateErrorType\022\027\n" - "\023UPDATE_MANUAL_ERROR\020\000\022\026\n\022UPDATE_FORCE_E" - "RROR\020\001\022\027\n\023UPDATE_SILENT_ERROR\020\002*k\n\022DiskC" - "acheErrorType\022 \n\034DISK_CACHE_UNAVAILABLE_" - "ERROR\020\000\022\036\n\032CANT_MOVE_DISK_CACHE_ERROR\020\001\022" - "\023\n\017DISK_FULL_ERROR\020\002*\335\001\n\033MailServerSetti" - "ngsErrorType\022\033\n\027IMAP_PORT_STARTUP_ERROR\020" - "\000\022\033\n\027SMTP_PORT_STARTUP_ERROR\020\001\022\032\n\026IMAP_P" - "ORT_CHANGE_ERROR\020\002\022\032\n\026SMTP_PORT_CHANGE_E" - "RROR\020\003\022%\n!IMAP_CONNECTION_MODE_CHANGE_ER" - "ROR\020\004\022%\n!SMTP_CONNECTION_MODE_CHANGE_ERR" - "OR\020\005*S\n\tErrorCode\022\021\n\rUNKNOWN_ERROR\020\000\022\031\n\025" - "TLS_CERT_EXPORT_ERROR\020\001\022\030\n\024TLS_KEY_EXPOR" - "T_ERROR\020\0022\360\035\n\006Bridge\022I\n\013CheckTokens\022\034.go" - "ogle.protobuf.StringValue\032\034.google.proto" - "buf.StringValue\022\?\n\013AddLogEntry\022\030.grpc.Ad" - "dLogEntryRequest\032\026.google.protobuf.Empty" - "\022:\n\010GuiReady\022\026.google.protobuf.Empty\032\026.g" - "rpc.GuiReadyResponse\0226\n\004Quit\022\026.google.pr" - "otobuf.Empty\032\026.google.protobuf.Empty\0229\n\007" - "Restart\022\026.google.protobuf.Empty\032\026.google" - ".protobuf.Empty\022C\n\rShowOnStartup\022\026.googl" - "e.protobuf.Empty\032\032.google.protobuf.BoolV" - "alue\022F\n\020SetIsAutostartOn\022\032.google.protob" - "uf.BoolValue\032\026.google.protobuf.Empty\022C\n\r" - "IsAutostartOn\022\026.google.protobuf.Empty\032\032." - "google.protobuf.BoolValue\022F\n\020SetIsBetaEn" - "abled\022\032.google.protobuf.BoolValue\032\026.goog" - "le.protobuf.Empty\022C\n\rIsBetaEnabled\022\026.goo" - "gle.protobuf.Empty\032\032.google.protobuf.Boo" - "lValue\022I\n\023SetIsAllMailVisible\022\032.google.p" - "rotobuf.BoolValue\032\026.google.protobuf.Empt" - "y\022F\n\020IsAllMailVisible\022\026.google.protobuf." - "Empty\032\032.google.protobuf.BoolValue\022<\n\004GoO" - "s\022\026.google.protobuf.Empty\032\034.google.proto" - "buf.StringValue\022>\n\014TriggerReset\022\026.google" - ".protobuf.Empty\032\026.google.protobuf.Empty\022" - "\?\n\007Version\022\026.google.protobuf.Empty\032\034.goo" - "gle.protobuf.StringValue\022@\n\010LogsPath\022\026.g" - "oogle.protobuf.Empty\032\034.google.protobuf.S" - "tringValue\022C\n\013LicensePath\022\026.google.proto" - "buf.Empty\032\034.google.protobuf.StringValue\022" - "L\n\024ReleaseNotesPageLink\022\026.google.protobu" - "f.Empty\032\034.google.protobuf.StringValue\022N\n" - "\026DependencyLicensesLink\022\026.google.protobu" - "f.Empty\032\034.google.protobuf.StringValue\022G\n" - "\017LandingPageLink\022\026.google.protobuf.Empty" - "\032\034.google.protobuf.StringValue\022J\n\022SetCol" - "orSchemeName\022\034.google.protobuf.StringVal" - "ue\032\026.google.protobuf.Empty\022G\n\017ColorSchem" - "eName\022\026.google.protobuf.Empty\032\034.google.p" - "rotobuf.StringValue\022J\n\022CurrentEmailClien" - "t\022\026.google.protobuf.Empty\032\034.google.proto" - "buf.StringValue\022;\n\tReportBug\022\026.grpc.Repo" - "rtBugRequest\032\026.google.protobuf.Empty\022M\n\025" - "ExportTLSCertificates\022\034.google.protobuf." - "StringValue\032\026.google.protobuf.Empty\022E\n\rF" - "orceLauncher\022\034.google.protobuf.StringVal" - "ue\032\026.google.protobuf.Empty\022I\n\021SetMainExe" - "cutable\022\034.google.protobuf.StringValue\032\026." - "google.protobuf.Empty\0223\n\005Login\022\022.grpc.Lo" - "ginRequest\032\026.google.protobuf.Empty\0226\n\010Lo" - "gin2FA\022\022.grpc.LoginRequest\032\026.google.prot" - "obuf.Empty\022=\n\017Login2Passwords\022\022.grpc.Log" - "inRequest\032\026.google.protobuf.Empty\022=\n\nLog" - "inAbort\022\027.grpc.LoginAbortRequest\032\026.googl" - "e.protobuf.Empty\022=\n\013CheckUpdate\022\026.google" - ".protobuf.Empty\032\026.google.protobuf.Empty\022" - "\?\n\rInstallUpdate\022\026.google.protobuf.Empty" - "\032\026.google.protobuf.Empty\022L\n\026SetIsAutomat" - "icUpdateOn\022\032.google.protobuf.BoolValue\032\026" - ".google.protobuf.Empty\022I\n\023IsAutomaticUpd" - "ateOn\022\026.google.protobuf.Empty\032\032.google.p" - "rotobuf.BoolValue\022E\n\rDiskCachePath\022\026.goo" - "gle.protobuf.Empty\032\034.google.protobuf.Str" - "ingValue\022H\n\020SetDiskCachePath\022\034.google.pr" - "otobuf.StringValue\032\026.google.protobuf.Emp" - "ty\022E\n\017SetIsDoHEnabled\022\032.google.protobuf." - "BoolValue\032\026.google.protobuf.Empty\022B\n\014IsD" - "oHEnabled\022\026.google.protobuf.Empty\032\032.goog" - "le.protobuf.BoolValue\022D\n\022MailServerSetti" - "ngs\022\026.google.protobuf.Empty\032\026.grpc.ImapS" - "mtpSettings\022G\n\025SetMailServerSettings\022\026.g" - "rpc.ImapSmtpSettings\032\026.google.protobuf.E" - "mpty\022@\n\010Hostname\022\026.google.protobuf.Empty" - "\032\034.google.protobuf.StringValue\022E\n\nIsPort" - "Free\022\033.google.protobuf.Int32Value\032\032.goog" - "le.protobuf.BoolValue\022N\n\022AvailableKeycha" - "ins\022\026.google.protobuf.Empty\032 .grpc.Avail" - "ableKeychainsResponse\022J\n\022SetCurrentKeych" - "ain\022\034.google.protobuf.StringValue\032\026.goog" - "le.protobuf.Empty\022G\n\017CurrentKeychain\022\026.g" - "oogle.protobuf.Empty\032\034.google.protobuf.S" - "tringValue\022=\n\013GetUserList\022\026.google.proto" - "buf.Empty\032\026.grpc.UserListResponse\0223\n\007Get" - "User\022\034.google.protobuf.StringValue\032\n.grp" - "c.User\022F\n\020SetUserSplitMode\022\032.grpc.UserSp" - "litModeRequest\032\026.google.protobuf.Empty\022U" - "\n\030SendBadEventUserFeedback\022!.grpc.UserBa" - "dEventFeedbackRequest\032\026.google.protobuf." - "Empty\022B\n\nLogoutUser\022\034.google.protobuf.St" - "ringValue\032\026.google.protobuf.Empty\022B\n\nRem" - "oveUser\022\034.google.protobuf.StringValue\032\026." - "google.protobuf.Empty\022Q\n\026ConfigureUserAp" - "pleMail\022\037.grpc.ConfigureAppleMailRequest" - "\032\026.google.protobuf.Empty\022\?\n\016RunEventStre" - "am\022\030.grpc.EventStreamRequest\032\021.grpc.Stre" - "amEvent0\001\022A\n\017StopEventStream\022\026.google.pr" - "otobuf.Empty\032\026.google.protobuf.EmptyB6Z4" - "github.com/ProtonMail/proton-bridge/v3/i" - "nternal/grpcb\006proto3" + "\022<\n\025usedBytesChangedEvent\030\005 \001(\0132\033.grpc.U" + "sedBytesChangedEventH\000\022:\n\024imapLoginFaile" + "dEvent\030\006 \001(\0132\032.grpc.ImapLoginFailedEvent" + "H\000\0222\n\020syncStartedEvent\030\007 \001(\0132\026.grpc.Sync" + "StartedEventH\000\0224\n\021syncFinishedEvent\030\010 \001(" + "\0132\027.grpc.SyncFinishedEventH\000\0224\n\021syncProg" + "ressEvent\030\t \001(\0132\027.grpc.SyncProgressEvent" + "H\000B\007\n\005event\".\n\034ToggleSplitModeFinishedEv" + "ent\022\016\n\006userID\030\001 \001(\t\")\n\025UserDisconnectedE" + "vent\022\020\n\010username\030\001 \001(\t\"\"\n\020UserChangedEve" + "nt\022\016\n\006userID\030\001 \001(\t\"4\n\014UserBadEvent\022\016\n\006us" + "erID\030\001 \001(\t\022\024\n\014errorMessage\030\002 \001(\t\":\n\025Used" + "BytesChangedEvent\022\016\n\006userID\030\001 \001(\t\022\021\n\tuse" + "dBytes\030\002 \001(\003\"(\n\024ImapLoginFailedEvent\022\020\n\010" + "username\030\001 \001(\t\"\"\n\020SyncStartedEvent\022\016\n\006us" + "erID\030\001 \001(\t\"#\n\021SyncFinishedEvent\022\016\n\006userI" + "D\030\001 \001(\t\"]\n\021SyncProgressEvent\022\016\n\006userID\030\001" + " \001(\t\022\020\n\010progress\030\002 \001(\001\022\021\n\telapsedMs\030\003 \001(" + "\003\022\023\n\013remainingMs\030\004 \001(\003\"2\n\021GenericErrorEv" + "ent\022\035\n\004code\030\001 \001(\0162\017.grpc.ErrorCode*q\n\010Lo" + "gLevel\022\r\n\tLOG_PANIC\020\000\022\r\n\tLOG_FATAL\020\001\022\r\n\t" + "LOG_ERROR\020\002\022\014\n\010LOG_WARN\020\003\022\014\n\010LOG_INFO\020\004\022" + "\r\n\tLOG_DEBUG\020\005\022\r\n\tLOG_TRACE\020\006*6\n\tUserSta" + "te\022\016\n\nSIGNED_OUT\020\000\022\n\n\006LOCKED\020\001\022\r\n\tCONNEC" + "TED\020\002*\242\001\n\016LoginErrorType\022\033\n\027USERNAME_PAS" + "SWORD_ERROR\020\000\022\r\n\tFREE_USER\020\001\022\024\n\020CONNECTI" + "ON_ERROR\020\002\022\r\n\tTFA_ERROR\020\003\022\r\n\tTFA_ABORT\020\004" + "\022\027\n\023TWO_PASSWORDS_ERROR\020\005\022\027\n\023TWO_PASSWOR" + "DS_ABORT\020\006*[\n\017UpdateErrorType\022\027\n\023UPDATE_" + "MANUAL_ERROR\020\000\022\026\n\022UPDATE_FORCE_ERROR\020\001\022\027" + "\n\023UPDATE_SILENT_ERROR\020\002*k\n\022DiskCacheErro" + "rType\022 \n\034DISK_CACHE_UNAVAILABLE_ERROR\020\000\022" + "\036\n\032CANT_MOVE_DISK_CACHE_ERROR\020\001\022\023\n\017DISK_" + "FULL_ERROR\020\002*\335\001\n\033MailServerSettingsError" + "Type\022\033\n\027IMAP_PORT_STARTUP_ERROR\020\000\022\033\n\027SMT" + "P_PORT_STARTUP_ERROR\020\001\022\032\n\026IMAP_PORT_CHAN" + "GE_ERROR\020\002\022\032\n\026SMTP_PORT_CHANGE_ERROR\020\003\022%" + "\n!IMAP_CONNECTION_MODE_CHANGE_ERROR\020\004\022%\n" + "!SMTP_CONNECTION_MODE_CHANGE_ERROR\020\005*S\n\t" + "ErrorCode\022\021\n\rUNKNOWN_ERROR\020\000\022\031\n\025TLS_CERT" + "_EXPORT_ERROR\020\001\022\030\n\024TLS_KEY_EXPORT_ERROR\020" + "\0022\360\035\n\006Bridge\022I\n\013CheckTokens\022\034.google.pro" + "tobuf.StringValue\032\034.google.protobuf.Stri" + "ngValue\022\?\n\013AddLogEntry\022\030.grpc.AddLogEntr" + "yRequest\032\026.google.protobuf.Empty\022:\n\010GuiR" + "eady\022\026.google.protobuf.Empty\032\026.grpc.GuiR" + "eadyResponse\0226\n\004Quit\022\026.google.protobuf.E" + "mpty\032\026.google.protobuf.Empty\0229\n\007Restart\022" + "\026.google.protobuf.Empty\032\026.google.protobu" + "f.Empty\022C\n\rShowOnStartup\022\026.google.protob" + "uf.Empty\032\032.google.protobuf.BoolValue\022F\n\020" + "SetIsAutostartOn\022\032.google.protobuf.BoolV" + "alue\032\026.google.protobuf.Empty\022C\n\rIsAutost" + "artOn\022\026.google.protobuf.Empty\032\032.google.p" + "rotobuf.BoolValue\022F\n\020SetIsBetaEnabled\022\032." + "google.protobuf.BoolValue\032\026.google.proto" + "buf.Empty\022C\n\rIsBetaEnabled\022\026.google.prot" + "obuf.Empty\032\032.google.protobuf.BoolValue\022I" + "\n\023SetIsAllMailVisible\022\032.google.protobuf." + "BoolValue\032\026.google.protobuf.Empty\022F\n\020IsA" + "llMailVisible\022\026.google.protobuf.Empty\032\032." + "google.protobuf.BoolValue\022<\n\004GoOs\022\026.goog" + "le.protobuf.Empty\032\034.google.protobuf.Stri" + "ngValue\022>\n\014TriggerReset\022\026.google.protobu" + "f.Empty\032\026.google.protobuf.Empty\022\?\n\007Versi" + "on\022\026.google.protobuf.Empty\032\034.google.prot" + "obuf.StringValue\022@\n\010LogsPath\022\026.google.pr" + "otobuf.Empty\032\034.google.protobuf.StringVal" + "ue\022C\n\013LicensePath\022\026.google.protobuf.Empt" + "y\032\034.google.protobuf.StringValue\022L\n\024Relea" + "seNotesPageLink\022\026.google.protobuf.Empty\032" + "\034.google.protobuf.StringValue\022N\n\026Depende" + "ncyLicensesLink\022\026.google.protobuf.Empty\032" + "\034.google.protobuf.StringValue\022G\n\017Landing" + "PageLink\022\026.google.protobuf.Empty\032\034.googl" + "e.protobuf.StringValue\022J\n\022SetColorScheme" + "Name\022\034.google.protobuf.StringValue\032\026.goo" + "gle.protobuf.Empty\022G\n\017ColorSchemeName\022\026." + "google.protobuf.Empty\032\034.google.protobuf." + "StringValue\022J\n\022CurrentEmailClient\022\026.goog" + "le.protobuf.Empty\032\034.google.protobuf.Stri" + "ngValue\022;\n\tReportBug\022\026.grpc.ReportBugReq" + "uest\032\026.google.protobuf.Empty\022M\n\025ExportTL" + "SCertificates\022\034.google.protobuf.StringVa" + "lue\032\026.google.protobuf.Empty\022E\n\rForceLaun" + "cher\022\034.google.protobuf.StringValue\032\026.goo" + "gle.protobuf.Empty\022I\n\021SetMainExecutable\022" + "\034.google.protobuf.StringValue\032\026.google.p" + "rotobuf.Empty\0223\n\005Login\022\022.grpc.LoginReque" + "st\032\026.google.protobuf.Empty\0226\n\010Login2FA\022\022" + ".grpc.LoginRequest\032\026.google.protobuf.Emp" + "ty\022=\n\017Login2Passwords\022\022.grpc.LoginReques" + "t\032\026.google.protobuf.Empty\022=\n\nLoginAbort\022" + "\027.grpc.LoginAbortRequest\032\026.google.protob" + "uf.Empty\022=\n\013CheckUpdate\022\026.google.protobu" + "f.Empty\032\026.google.protobuf.Empty\022\?\n\rInsta" + "llUpdate\022\026.google.protobuf.Empty\032\026.googl" + "e.protobuf.Empty\022L\n\026SetIsAutomaticUpdate" + "On\022\032.google.protobuf.BoolValue\032\026.google." + "protobuf.Empty\022I\n\023IsAutomaticUpdateOn\022\026." + "google.protobuf.Empty\032\032.google.protobuf." + "BoolValue\022E\n\rDiskCachePath\022\026.google.prot" + "obuf.Empty\032\034.google.protobuf.StringValue" + "\022H\n\020SetDiskCachePath\022\034.google.protobuf.S" + "tringValue\032\026.google.protobuf.Empty\022E\n\017Se" + "tIsDoHEnabled\022\032.google.protobuf.BoolValu" + "e\032\026.google.protobuf.Empty\022B\n\014IsDoHEnable" + "d\022\026.google.protobuf.Empty\032\032.google.proto" + "buf.BoolValue\022D\n\022MailServerSettings\022\026.go" + "ogle.protobuf.Empty\032\026.grpc.ImapSmtpSetti" + "ngs\022G\n\025SetMailServerSettings\022\026.grpc.Imap" + "SmtpSettings\032\026.google.protobuf.Empty\022@\n\010" + "Hostname\022\026.google.protobuf.Empty\032\034.googl" + "e.protobuf.StringValue\022E\n\nIsPortFree\022\033.g" + "oogle.protobuf.Int32Value\032\032.google.proto" + "buf.BoolValue\022N\n\022AvailableKeychains\022\026.go" + "ogle.protobuf.Empty\032 .grpc.AvailableKeyc" + "hainsResponse\022J\n\022SetCurrentKeychain\022\034.go" + "ogle.protobuf.StringValue\032\026.google.proto" + "buf.Empty\022G\n\017CurrentKeychain\022\026.google.pr" + "otobuf.Empty\032\034.google.protobuf.StringVal" + "ue\022=\n\013GetUserList\022\026.google.protobuf.Empt" + "y\032\026.grpc.UserListResponse\0223\n\007GetUser\022\034.g" + "oogle.protobuf.StringValue\032\n.grpc.User\022F" + "\n\020SetUserSplitMode\022\032.grpc.UserSplitModeR" + "equest\032\026.google.protobuf.Empty\022U\n\030SendBa" + "dEventUserFeedback\022!.grpc.UserBadEventFe" + "edbackRequest\032\026.google.protobuf.Empty\022B\n" + "\nLogoutUser\022\034.google.protobuf.StringValu" + "e\032\026.google.protobuf.Empty\022B\n\nRemoveUser\022" + "\034.google.protobuf.StringValue\032\026.google.p" + "rotobuf.Empty\022Q\n\026ConfigureUserAppleMail\022" + "\037.grpc.ConfigureAppleMailRequest\032\026.googl" + "e.protobuf.Empty\022\?\n\016RunEventStream\022\030.grp" + "c.EventStreamRequest\032\021.grpc.StreamEvent0" + "\001\022A\n\017StopEventStream\022\026.google.protobuf.E" + "mpty\032\026.google.protobuf.EmptyB6Z4github.c" + "om/ProtonMail/proton-bridge/v3/internal/" + "grpcb\006proto3" ; static const ::_pbi::DescriptorTable* const descriptor_table_bridge_2eproto_deps[2] = { &::descriptor_table_google_2fprotobuf_2fempty_2eproto, @@ -1641,9 +1778,9 @@ static const ::_pbi::DescriptorTable* const descriptor_table_bridge_2eproto_deps }; static ::_pbi::once_flag descriptor_table_bridge_2eproto_once; const ::_pbi::DescriptorTable descriptor_table_bridge_2eproto = { - false, false, 9980, descriptor_table_protodef_bridge_2eproto, + false, false, 10532, descriptor_table_protodef_bridge_2eproto, "bridge.proto", - &descriptor_table_bridge_2eproto_once, descriptor_table_bridge_2eproto_deps, 2, 59, + &descriptor_table_bridge_2eproto_once, descriptor_table_bridge_2eproto_deps, 2, 64, schemas, file_default_instances, TableStruct_bridge_2eproto::offsets, file_level_metadata_bridge_2eproto, file_level_enum_descriptors_bridge_2eproto, file_level_service_descriptors_bridge_2eproto, @@ -12636,6 +12773,11 @@ class UserEvent::_Internal { static const ::grpc::UserDisconnectedEvent& userdisconnected(const UserEvent* msg); static const ::grpc::UserChangedEvent& userchanged(const UserEvent* msg); static const ::grpc::UserBadEvent& userbadevent(const UserEvent* msg); + static const ::grpc::UsedBytesChangedEvent& usedbyteschangedevent(const UserEvent* msg); + static const ::grpc::ImapLoginFailedEvent& imaploginfailedevent(const UserEvent* msg); + static const ::grpc::SyncStartedEvent& syncstartedevent(const UserEvent* msg); + static const ::grpc::SyncFinishedEvent& syncfinishedevent(const UserEvent* msg); + static const ::grpc::SyncProgressEvent& syncprogressevent(const UserEvent* msg); }; const ::grpc::ToggleSplitModeFinishedEvent& @@ -12654,6 +12796,26 @@ const ::grpc::UserBadEvent& UserEvent::_Internal::userbadevent(const UserEvent* msg) { return *msg->_impl_.event_.userbadevent_; } +const ::grpc::UsedBytesChangedEvent& +UserEvent::_Internal::usedbyteschangedevent(const UserEvent* msg) { + return *msg->_impl_.event_.usedbyteschangedevent_; +} +const ::grpc::ImapLoginFailedEvent& +UserEvent::_Internal::imaploginfailedevent(const UserEvent* msg) { + return *msg->_impl_.event_.imaploginfailedevent_; +} +const ::grpc::SyncStartedEvent& +UserEvent::_Internal::syncstartedevent(const UserEvent* msg) { + return *msg->_impl_.event_.syncstartedevent_; +} +const ::grpc::SyncFinishedEvent& +UserEvent::_Internal::syncfinishedevent(const UserEvent* msg) { + return *msg->_impl_.event_.syncfinishedevent_; +} +const ::grpc::SyncProgressEvent& +UserEvent::_Internal::syncprogressevent(const UserEvent* msg) { + return *msg->_impl_.event_.syncprogressevent_; +} void UserEvent::set_allocated_togglesplitmodefinished(::grpc::ToggleSplitModeFinishedEvent* togglesplitmodefinished) { ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); clear_event(); @@ -12714,6 +12876,81 @@ void UserEvent::set_allocated_userbadevent(::grpc::UserBadEvent* userbadevent) { } // @@protoc_insertion_point(field_set_allocated:grpc.UserEvent.userBadEvent) } +void UserEvent::set_allocated_usedbyteschangedevent(::grpc::UsedBytesChangedEvent* usedbyteschangedevent) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + clear_event(); + if (usedbyteschangedevent) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(usedbyteschangedevent); + if (message_arena != submessage_arena) { + usedbyteschangedevent = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, usedbyteschangedevent, submessage_arena); + } + set_has_usedbyteschangedevent(); + _impl_.event_.usedbyteschangedevent_ = usedbyteschangedevent; + } + // @@protoc_insertion_point(field_set_allocated:grpc.UserEvent.usedBytesChangedEvent) +} +void UserEvent::set_allocated_imaploginfailedevent(::grpc::ImapLoginFailedEvent* imaploginfailedevent) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + clear_event(); + if (imaploginfailedevent) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(imaploginfailedevent); + if (message_arena != submessage_arena) { + imaploginfailedevent = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, imaploginfailedevent, submessage_arena); + } + set_has_imaploginfailedevent(); + _impl_.event_.imaploginfailedevent_ = imaploginfailedevent; + } + // @@protoc_insertion_point(field_set_allocated:grpc.UserEvent.imapLoginFailedEvent) +} +void UserEvent::set_allocated_syncstartedevent(::grpc::SyncStartedEvent* syncstartedevent) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + clear_event(); + if (syncstartedevent) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(syncstartedevent); + if (message_arena != submessage_arena) { + syncstartedevent = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, syncstartedevent, submessage_arena); + } + set_has_syncstartedevent(); + _impl_.event_.syncstartedevent_ = syncstartedevent; + } + // @@protoc_insertion_point(field_set_allocated:grpc.UserEvent.syncStartedEvent) +} +void UserEvent::set_allocated_syncfinishedevent(::grpc::SyncFinishedEvent* syncfinishedevent) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + clear_event(); + if (syncfinishedevent) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(syncfinishedevent); + if (message_arena != submessage_arena) { + syncfinishedevent = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, syncfinishedevent, submessage_arena); + } + set_has_syncfinishedevent(); + _impl_.event_.syncfinishedevent_ = syncfinishedevent; + } + // @@protoc_insertion_point(field_set_allocated:grpc.UserEvent.syncFinishedEvent) +} +void UserEvent::set_allocated_syncprogressevent(::grpc::SyncProgressEvent* syncprogressevent) { + ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation(); + clear_event(); + if (syncprogressevent) { + ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena = + ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(syncprogressevent); + if (message_arena != submessage_arena) { + syncprogressevent = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage( + message_arena, syncprogressevent, submessage_arena); + } + set_has_syncprogressevent(); + _impl_.event_.syncprogressevent_ = syncprogressevent; + } + // @@protoc_insertion_point(field_set_allocated:grpc.UserEvent.syncProgressEvent) +} UserEvent::UserEvent(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned) : ::PROTOBUF_NAMESPACE_ID::Message(arena, is_message_owned) { @@ -12751,6 +12988,31 @@ UserEvent::UserEvent(const UserEvent& from) from._internal_userbadevent()); break; } + case kUsedBytesChangedEvent: { + _this->_internal_mutable_usedbyteschangedevent()->::grpc::UsedBytesChangedEvent::MergeFrom( + from._internal_usedbyteschangedevent()); + break; + } + case kImapLoginFailedEvent: { + _this->_internal_mutable_imaploginfailedevent()->::grpc::ImapLoginFailedEvent::MergeFrom( + from._internal_imaploginfailedevent()); + break; + } + case kSyncStartedEvent: { + _this->_internal_mutable_syncstartedevent()->::grpc::SyncStartedEvent::MergeFrom( + from._internal_syncstartedevent()); + break; + } + case kSyncFinishedEvent: { + _this->_internal_mutable_syncfinishedevent()->::grpc::SyncFinishedEvent::MergeFrom( + from._internal_syncfinishedevent()); + break; + } + case kSyncProgressEvent: { + _this->_internal_mutable_syncprogressevent()->::grpc::SyncProgressEvent::MergeFrom( + from._internal_syncprogressevent()); + break; + } case EVENT_NOT_SET: { break; } @@ -12817,6 +13079,36 @@ void UserEvent::clear_event() { } break; } + case kUsedBytesChangedEvent: { + if (GetArenaForAllocation() == nullptr) { + delete _impl_.event_.usedbyteschangedevent_; + } + break; + } + case kImapLoginFailedEvent: { + if (GetArenaForAllocation() == nullptr) { + delete _impl_.event_.imaploginfailedevent_; + } + break; + } + case kSyncStartedEvent: { + if (GetArenaForAllocation() == nullptr) { + delete _impl_.event_.syncstartedevent_; + } + break; + } + case kSyncFinishedEvent: { + if (GetArenaForAllocation() == nullptr) { + delete _impl_.event_.syncfinishedevent_; + } + break; + } + case kSyncProgressEvent: { + if (GetArenaForAllocation() == nullptr) { + delete _impl_.event_.syncprogressevent_; + } + break; + } case EVENT_NOT_SET: { break; } @@ -12873,6 +13165,46 @@ const char* UserEvent::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx } else goto handle_unusual; continue; + // .grpc.UsedBytesChangedEvent usedBytesChangedEvent = 5; + case 5: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 42)) { + ptr = ctx->ParseMessage(_internal_mutable_usedbyteschangedevent(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // .grpc.ImapLoginFailedEvent imapLoginFailedEvent = 6; + case 6: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 50)) { + ptr = ctx->ParseMessage(_internal_mutable_imaploginfailedevent(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // .grpc.SyncStartedEvent syncStartedEvent = 7; + case 7: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 58)) { + ptr = ctx->ParseMessage(_internal_mutable_syncstartedevent(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // .grpc.SyncFinishedEvent syncFinishedEvent = 8; + case 8: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 66)) { + ptr = ctx->ParseMessage(_internal_mutable_syncfinishedevent(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // .grpc.SyncProgressEvent syncProgressEvent = 9; + case 9: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 74)) { + ptr = ctx->ParseMessage(_internal_mutable_syncprogressevent(), ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; default: goto handle_unusual; } // switch @@ -12930,6 +13262,41 @@ uint8_t* UserEvent::_InternalSerialize( _Internal::userbadevent(this).GetCachedSize(), target, stream); } + // .grpc.UsedBytesChangedEvent usedBytesChangedEvent = 5; + if (_internal_has_usedbyteschangedevent()) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(5, _Internal::usedbyteschangedevent(this), + _Internal::usedbyteschangedevent(this).GetCachedSize(), target, stream); + } + + // .grpc.ImapLoginFailedEvent imapLoginFailedEvent = 6; + if (_internal_has_imaploginfailedevent()) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(6, _Internal::imaploginfailedevent(this), + _Internal::imaploginfailedevent(this).GetCachedSize(), target, stream); + } + + // .grpc.SyncStartedEvent syncStartedEvent = 7; + if (_internal_has_syncstartedevent()) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(7, _Internal::syncstartedevent(this), + _Internal::syncstartedevent(this).GetCachedSize(), target, stream); + } + + // .grpc.SyncFinishedEvent syncFinishedEvent = 8; + if (_internal_has_syncfinishedevent()) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(8, _Internal::syncfinishedevent(this), + _Internal::syncfinishedevent(this).GetCachedSize(), target, stream); + } + + // .grpc.SyncProgressEvent syncProgressEvent = 9; + if (_internal_has_syncprogressevent()) { + target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite:: + InternalWriteMessage(9, _Internal::syncprogressevent(this), + _Internal::syncprogressevent(this).GetCachedSize(), target, stream); + } + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { target = ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( _internal_metadata_.unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(::PROTOBUF_NAMESPACE_ID::UnknownFieldSet::default_instance), target, stream); @@ -12975,6 +13342,41 @@ size_t UserEvent::ByteSizeLong() const { *_impl_.event_.userbadevent_); break; } + // .grpc.UsedBytesChangedEvent usedBytesChangedEvent = 5; + case kUsedBytesChangedEvent: { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.event_.usedbyteschangedevent_); + break; + } + // .grpc.ImapLoginFailedEvent imapLoginFailedEvent = 6; + case kImapLoginFailedEvent: { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.event_.imaploginfailedevent_); + break; + } + // .grpc.SyncStartedEvent syncStartedEvent = 7; + case kSyncStartedEvent: { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.event_.syncstartedevent_); + break; + } + // .grpc.SyncFinishedEvent syncFinishedEvent = 8; + case kSyncFinishedEvent: { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.event_.syncfinishedevent_); + break; + } + // .grpc.SyncProgressEvent syncProgressEvent = 9; + case kSyncProgressEvent: { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize( + *_impl_.event_.syncprogressevent_); + break; + } case EVENT_NOT_SET: { break; } @@ -13018,6 +13420,31 @@ void UserEvent::MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROT from._internal_userbadevent()); break; } + case kUsedBytesChangedEvent: { + _this->_internal_mutable_usedbyteschangedevent()->::grpc::UsedBytesChangedEvent::MergeFrom( + from._internal_usedbyteschangedevent()); + break; + } + case kImapLoginFailedEvent: { + _this->_internal_mutable_imaploginfailedevent()->::grpc::ImapLoginFailedEvent::MergeFrom( + from._internal_imaploginfailedevent()); + break; + } + case kSyncStartedEvent: { + _this->_internal_mutable_syncstartedevent()->::grpc::SyncStartedEvent::MergeFrom( + from._internal_syncstartedevent()); + break; + } + case kSyncFinishedEvent: { + _this->_internal_mutable_syncfinishedevent()->::grpc::SyncFinishedEvent::MergeFrom( + from._internal_syncfinishedevent()); + break; + } + case kSyncProgressEvent: { + _this->_internal_mutable_syncprogressevent()->::grpc::SyncProgressEvent::MergeFrom( + from._internal_syncprogressevent()); + break; + } case EVENT_NOT_SET: { break; } @@ -13913,6 +14340,1144 @@ void UserBadEvent::InternalSwap(UserBadEvent* other) { // =================================================================== +class UsedBytesChangedEvent::_Internal { + public: +}; + +UsedBytesChangedEvent::UsedBytesChangedEvent(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::Message(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:grpc.UsedBytesChangedEvent) +} +UsedBytesChangedEvent::UsedBytesChangedEvent(const UsedBytesChangedEvent& from) + : ::PROTOBUF_NAMESPACE_ID::Message() { + UsedBytesChangedEvent* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_.userid_){} + , decltype(_impl_.usedbytes_){} + , /*decltype(_impl_._cached_size_)*/{}}; + + _internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); + _impl_.userid_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.userid_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (!from._internal_userid().empty()) { + _this->_impl_.userid_.Set(from._internal_userid(), + _this->GetArenaForAllocation()); + } + _this->_impl_.usedbytes_ = from._impl_.usedbytes_; + // @@protoc_insertion_point(copy_constructor:grpc.UsedBytesChangedEvent) +} + +inline void UsedBytesChangedEvent::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_.userid_){} + , decltype(_impl_.usedbytes_){int64_t{0}} + , /*decltype(_impl_._cached_size_)*/{} + }; + _impl_.userid_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.userid_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING +} + +UsedBytesChangedEvent::~UsedBytesChangedEvent() { + // @@protoc_insertion_point(destructor:grpc.UsedBytesChangedEvent) + if (auto *arena = _internal_metadata_.DeleteReturnArena<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void UsedBytesChangedEvent::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); + _impl_.userid_.Destroy(); +} + +void UsedBytesChangedEvent::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void UsedBytesChangedEvent::Clear() { +// @@protoc_insertion_point(message_clear_start:grpc.UsedBytesChangedEvent) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + _impl_.userid_.ClearToEmpty(); + _impl_.usedbytes_ = int64_t{0}; + _internal_metadata_.Clear<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(); +} + +const char* UsedBytesChangedEvent::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // string userID = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 10)) { + auto str = _internal_mutable_userid(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + CHK_(::_pbi::VerifyUTF8(str, "grpc.UsedBytesChangedEvent.userID")); + } else + goto handle_unusual; + continue; + // int64 usedBytes = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 16)) { + _impl_.usedbytes_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* UsedBytesChangedEvent::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:grpc.UsedBytesChangedEvent) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + // string userID = 1; + if (!this->_internal_userid().empty()) { + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::VerifyUtf8String( + this->_internal_userid().data(), static_cast(this->_internal_userid().length()), + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::SERIALIZE, + "grpc.UsedBytesChangedEvent.userID"); + target = stream->WriteStringMaybeAliased( + 1, this->_internal_userid(), target); + } + + // int64 usedBytes = 2; + if (this->_internal_usedbytes() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteInt64ToArray(2, this->_internal_usedbytes(), target); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( + _internal_metadata_.unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(::PROTOBUF_NAMESPACE_ID::UnknownFieldSet::default_instance), target, stream); + } + // @@protoc_insertion_point(serialize_to_array_end:grpc.UsedBytesChangedEvent) + return target; +} + +size_t UsedBytesChangedEvent::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:grpc.UsedBytesChangedEvent) + size_t total_size = 0; + + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + // string userID = 1; + if (!this->_internal_userid().empty()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_userid()); + } + + // int64 usedBytes = 2; + if (this->_internal_usedbytes() != 0) { + total_size += ::_pbi::WireFormatLite::Int64SizePlusOne(this->_internal_usedbytes()); + } + + return MaybeComputeUnknownFieldsSize(total_size, &_impl_._cached_size_); +} + +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData UsedBytesChangedEvent::_class_data_ = { + ::PROTOBUF_NAMESPACE_ID::Message::CopyWithSourceCheck, + UsedBytesChangedEvent::MergeImpl +}; +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*UsedBytesChangedEvent::GetClassData() const { return &_class_data_; } + + +void UsedBytesChangedEvent::MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg) { + auto* const _this = static_cast(&to_msg); + auto& from = static_cast(from_msg); + // @@protoc_insertion_point(class_specific_merge_from_start:grpc.UsedBytesChangedEvent) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + if (!from._internal_userid().empty()) { + _this->_internal_set_userid(from._internal_userid()); + } + if (from._internal_usedbytes() != 0) { + _this->_internal_set_usedbytes(from._internal_usedbytes()); + } + _this->_internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); +} + +void UsedBytesChangedEvent::CopyFrom(const UsedBytesChangedEvent& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:grpc.UsedBytesChangedEvent) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool UsedBytesChangedEvent::IsInitialized() const { + return true; +} + +void UsedBytesChangedEvent::InternalSwap(UsedBytesChangedEvent* other) { + using std::swap; + auto* lhs_arena = GetArenaForAllocation(); + auto* rhs_arena = other->GetArenaForAllocation(); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.userid_, lhs_arena, + &other->_impl_.userid_, rhs_arena + ); + swap(_impl_.usedbytes_, other->_impl_.usedbytes_); +} + +::PROTOBUF_NAMESPACE_ID::Metadata UsedBytesChangedEvent::GetMetadata() const { + return ::_pbi::AssignDescriptors( + &descriptor_table_bridge_2eproto_getter, &descriptor_table_bridge_2eproto_once, + file_level_metadata_bridge_2eproto[58]); +} + +// =================================================================== + +class ImapLoginFailedEvent::_Internal { + public: +}; + +ImapLoginFailedEvent::ImapLoginFailedEvent(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::Message(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:grpc.ImapLoginFailedEvent) +} +ImapLoginFailedEvent::ImapLoginFailedEvent(const ImapLoginFailedEvent& from) + : ::PROTOBUF_NAMESPACE_ID::Message() { + ImapLoginFailedEvent* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_.username_){} + , /*decltype(_impl_._cached_size_)*/{}}; + + _internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); + _impl_.username_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.username_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (!from._internal_username().empty()) { + _this->_impl_.username_.Set(from._internal_username(), + _this->GetArenaForAllocation()); + } + // @@protoc_insertion_point(copy_constructor:grpc.ImapLoginFailedEvent) +} + +inline void ImapLoginFailedEvent::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_.username_){} + , /*decltype(_impl_._cached_size_)*/{} + }; + _impl_.username_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.username_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING +} + +ImapLoginFailedEvent::~ImapLoginFailedEvent() { + // @@protoc_insertion_point(destructor:grpc.ImapLoginFailedEvent) + if (auto *arena = _internal_metadata_.DeleteReturnArena<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void ImapLoginFailedEvent::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); + _impl_.username_.Destroy(); +} + +void ImapLoginFailedEvent::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void ImapLoginFailedEvent::Clear() { +// @@protoc_insertion_point(message_clear_start:grpc.ImapLoginFailedEvent) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + _impl_.username_.ClearToEmpty(); + _internal_metadata_.Clear<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(); +} + +const char* ImapLoginFailedEvent::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // string username = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 10)) { + auto str = _internal_mutable_username(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + CHK_(::_pbi::VerifyUTF8(str, "grpc.ImapLoginFailedEvent.username")); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* ImapLoginFailedEvent::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:grpc.ImapLoginFailedEvent) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + // string username = 1; + if (!this->_internal_username().empty()) { + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::VerifyUtf8String( + this->_internal_username().data(), static_cast(this->_internal_username().length()), + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::SERIALIZE, + "grpc.ImapLoginFailedEvent.username"); + target = stream->WriteStringMaybeAliased( + 1, this->_internal_username(), target); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( + _internal_metadata_.unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(::PROTOBUF_NAMESPACE_ID::UnknownFieldSet::default_instance), target, stream); + } + // @@protoc_insertion_point(serialize_to_array_end:grpc.ImapLoginFailedEvent) + return target; +} + +size_t ImapLoginFailedEvent::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:grpc.ImapLoginFailedEvent) + size_t total_size = 0; + + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + // string username = 1; + if (!this->_internal_username().empty()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_username()); + } + + return MaybeComputeUnknownFieldsSize(total_size, &_impl_._cached_size_); +} + +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData ImapLoginFailedEvent::_class_data_ = { + ::PROTOBUF_NAMESPACE_ID::Message::CopyWithSourceCheck, + ImapLoginFailedEvent::MergeImpl +}; +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*ImapLoginFailedEvent::GetClassData() const { return &_class_data_; } + + +void ImapLoginFailedEvent::MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg) { + auto* const _this = static_cast(&to_msg); + auto& from = static_cast(from_msg); + // @@protoc_insertion_point(class_specific_merge_from_start:grpc.ImapLoginFailedEvent) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + if (!from._internal_username().empty()) { + _this->_internal_set_username(from._internal_username()); + } + _this->_internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); +} + +void ImapLoginFailedEvent::CopyFrom(const ImapLoginFailedEvent& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:grpc.ImapLoginFailedEvent) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool ImapLoginFailedEvent::IsInitialized() const { + return true; +} + +void ImapLoginFailedEvent::InternalSwap(ImapLoginFailedEvent* other) { + using std::swap; + auto* lhs_arena = GetArenaForAllocation(); + auto* rhs_arena = other->GetArenaForAllocation(); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.username_, lhs_arena, + &other->_impl_.username_, rhs_arena + ); +} + +::PROTOBUF_NAMESPACE_ID::Metadata ImapLoginFailedEvent::GetMetadata() const { + return ::_pbi::AssignDescriptors( + &descriptor_table_bridge_2eproto_getter, &descriptor_table_bridge_2eproto_once, + file_level_metadata_bridge_2eproto[59]); +} + +// =================================================================== + +class SyncStartedEvent::_Internal { + public: +}; + +SyncStartedEvent::SyncStartedEvent(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::Message(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:grpc.SyncStartedEvent) +} +SyncStartedEvent::SyncStartedEvent(const SyncStartedEvent& from) + : ::PROTOBUF_NAMESPACE_ID::Message() { + SyncStartedEvent* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_.userid_){} + , /*decltype(_impl_._cached_size_)*/{}}; + + _internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); + _impl_.userid_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.userid_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (!from._internal_userid().empty()) { + _this->_impl_.userid_.Set(from._internal_userid(), + _this->GetArenaForAllocation()); + } + // @@protoc_insertion_point(copy_constructor:grpc.SyncStartedEvent) +} + +inline void SyncStartedEvent::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_.userid_){} + , /*decltype(_impl_._cached_size_)*/{} + }; + _impl_.userid_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.userid_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING +} + +SyncStartedEvent::~SyncStartedEvent() { + // @@protoc_insertion_point(destructor:grpc.SyncStartedEvent) + if (auto *arena = _internal_metadata_.DeleteReturnArena<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void SyncStartedEvent::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); + _impl_.userid_.Destroy(); +} + +void SyncStartedEvent::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void SyncStartedEvent::Clear() { +// @@protoc_insertion_point(message_clear_start:grpc.SyncStartedEvent) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + _impl_.userid_.ClearToEmpty(); + _internal_metadata_.Clear<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(); +} + +const char* SyncStartedEvent::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // string userID = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 10)) { + auto str = _internal_mutable_userid(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + CHK_(::_pbi::VerifyUTF8(str, "grpc.SyncStartedEvent.userID")); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* SyncStartedEvent::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:grpc.SyncStartedEvent) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + // string userID = 1; + if (!this->_internal_userid().empty()) { + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::VerifyUtf8String( + this->_internal_userid().data(), static_cast(this->_internal_userid().length()), + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::SERIALIZE, + "grpc.SyncStartedEvent.userID"); + target = stream->WriteStringMaybeAliased( + 1, this->_internal_userid(), target); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( + _internal_metadata_.unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(::PROTOBUF_NAMESPACE_ID::UnknownFieldSet::default_instance), target, stream); + } + // @@protoc_insertion_point(serialize_to_array_end:grpc.SyncStartedEvent) + return target; +} + +size_t SyncStartedEvent::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:grpc.SyncStartedEvent) + size_t total_size = 0; + + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + // string userID = 1; + if (!this->_internal_userid().empty()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_userid()); + } + + return MaybeComputeUnknownFieldsSize(total_size, &_impl_._cached_size_); +} + +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData SyncStartedEvent::_class_data_ = { + ::PROTOBUF_NAMESPACE_ID::Message::CopyWithSourceCheck, + SyncStartedEvent::MergeImpl +}; +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*SyncStartedEvent::GetClassData() const { return &_class_data_; } + + +void SyncStartedEvent::MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg) { + auto* const _this = static_cast(&to_msg); + auto& from = static_cast(from_msg); + // @@protoc_insertion_point(class_specific_merge_from_start:grpc.SyncStartedEvent) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + if (!from._internal_userid().empty()) { + _this->_internal_set_userid(from._internal_userid()); + } + _this->_internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); +} + +void SyncStartedEvent::CopyFrom(const SyncStartedEvent& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:grpc.SyncStartedEvent) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool SyncStartedEvent::IsInitialized() const { + return true; +} + +void SyncStartedEvent::InternalSwap(SyncStartedEvent* other) { + using std::swap; + auto* lhs_arena = GetArenaForAllocation(); + auto* rhs_arena = other->GetArenaForAllocation(); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.userid_, lhs_arena, + &other->_impl_.userid_, rhs_arena + ); +} + +::PROTOBUF_NAMESPACE_ID::Metadata SyncStartedEvent::GetMetadata() const { + return ::_pbi::AssignDescriptors( + &descriptor_table_bridge_2eproto_getter, &descriptor_table_bridge_2eproto_once, + file_level_metadata_bridge_2eproto[60]); +} + +// =================================================================== + +class SyncFinishedEvent::_Internal { + public: +}; + +SyncFinishedEvent::SyncFinishedEvent(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::Message(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:grpc.SyncFinishedEvent) +} +SyncFinishedEvent::SyncFinishedEvent(const SyncFinishedEvent& from) + : ::PROTOBUF_NAMESPACE_ID::Message() { + SyncFinishedEvent* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_.userid_){} + , /*decltype(_impl_._cached_size_)*/{}}; + + _internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); + _impl_.userid_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.userid_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (!from._internal_userid().empty()) { + _this->_impl_.userid_.Set(from._internal_userid(), + _this->GetArenaForAllocation()); + } + // @@protoc_insertion_point(copy_constructor:grpc.SyncFinishedEvent) +} + +inline void SyncFinishedEvent::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_.userid_){} + , /*decltype(_impl_._cached_size_)*/{} + }; + _impl_.userid_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.userid_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING +} + +SyncFinishedEvent::~SyncFinishedEvent() { + // @@protoc_insertion_point(destructor:grpc.SyncFinishedEvent) + if (auto *arena = _internal_metadata_.DeleteReturnArena<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void SyncFinishedEvent::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); + _impl_.userid_.Destroy(); +} + +void SyncFinishedEvent::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void SyncFinishedEvent::Clear() { +// @@protoc_insertion_point(message_clear_start:grpc.SyncFinishedEvent) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + _impl_.userid_.ClearToEmpty(); + _internal_metadata_.Clear<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(); +} + +const char* SyncFinishedEvent::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // string userID = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 10)) { + auto str = _internal_mutable_userid(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + CHK_(::_pbi::VerifyUTF8(str, "grpc.SyncFinishedEvent.userID")); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* SyncFinishedEvent::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:grpc.SyncFinishedEvent) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + // string userID = 1; + if (!this->_internal_userid().empty()) { + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::VerifyUtf8String( + this->_internal_userid().data(), static_cast(this->_internal_userid().length()), + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::SERIALIZE, + "grpc.SyncFinishedEvent.userID"); + target = stream->WriteStringMaybeAliased( + 1, this->_internal_userid(), target); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( + _internal_metadata_.unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(::PROTOBUF_NAMESPACE_ID::UnknownFieldSet::default_instance), target, stream); + } + // @@protoc_insertion_point(serialize_to_array_end:grpc.SyncFinishedEvent) + return target; +} + +size_t SyncFinishedEvent::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:grpc.SyncFinishedEvent) + size_t total_size = 0; + + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + // string userID = 1; + if (!this->_internal_userid().empty()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_userid()); + } + + return MaybeComputeUnknownFieldsSize(total_size, &_impl_._cached_size_); +} + +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData SyncFinishedEvent::_class_data_ = { + ::PROTOBUF_NAMESPACE_ID::Message::CopyWithSourceCheck, + SyncFinishedEvent::MergeImpl +}; +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*SyncFinishedEvent::GetClassData() const { return &_class_data_; } + + +void SyncFinishedEvent::MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg) { + auto* const _this = static_cast(&to_msg); + auto& from = static_cast(from_msg); + // @@protoc_insertion_point(class_specific_merge_from_start:grpc.SyncFinishedEvent) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + if (!from._internal_userid().empty()) { + _this->_internal_set_userid(from._internal_userid()); + } + _this->_internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); +} + +void SyncFinishedEvent::CopyFrom(const SyncFinishedEvent& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:grpc.SyncFinishedEvent) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool SyncFinishedEvent::IsInitialized() const { + return true; +} + +void SyncFinishedEvent::InternalSwap(SyncFinishedEvent* other) { + using std::swap; + auto* lhs_arena = GetArenaForAllocation(); + auto* rhs_arena = other->GetArenaForAllocation(); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.userid_, lhs_arena, + &other->_impl_.userid_, rhs_arena + ); +} + +::PROTOBUF_NAMESPACE_ID::Metadata SyncFinishedEvent::GetMetadata() const { + return ::_pbi::AssignDescriptors( + &descriptor_table_bridge_2eproto_getter, &descriptor_table_bridge_2eproto_once, + file_level_metadata_bridge_2eproto[61]); +} + +// =================================================================== + +class SyncProgressEvent::_Internal { + public: +}; + +SyncProgressEvent::SyncProgressEvent(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned) + : ::PROTOBUF_NAMESPACE_ID::Message(arena, is_message_owned) { + SharedCtor(arena, is_message_owned); + // @@protoc_insertion_point(arena_constructor:grpc.SyncProgressEvent) +} +SyncProgressEvent::SyncProgressEvent(const SyncProgressEvent& from) + : ::PROTOBUF_NAMESPACE_ID::Message() { + SyncProgressEvent* const _this = this; (void)_this; + new (&_impl_) Impl_{ + decltype(_impl_.userid_){} + , decltype(_impl_.progress_){} + , decltype(_impl_.elapsedms_){} + , decltype(_impl_.remainingms_){} + , /*decltype(_impl_._cached_size_)*/{}}; + + _internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); + _impl_.userid_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.userid_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (!from._internal_userid().empty()) { + _this->_impl_.userid_.Set(from._internal_userid(), + _this->GetArenaForAllocation()); + } + ::memcpy(&_impl_.progress_, &from._impl_.progress_, + static_cast(reinterpret_cast(&_impl_.remainingms_) - + reinterpret_cast(&_impl_.progress_)) + sizeof(_impl_.remainingms_)); + // @@protoc_insertion_point(copy_constructor:grpc.SyncProgressEvent) +} + +inline void SyncProgressEvent::SharedCtor( + ::_pb::Arena* arena, bool is_message_owned) { + (void)arena; + (void)is_message_owned; + new (&_impl_) Impl_{ + decltype(_impl_.userid_){} + , decltype(_impl_.progress_){0} + , decltype(_impl_.elapsedms_){int64_t{0}} + , decltype(_impl_.remainingms_){int64_t{0}} + , /*decltype(_impl_._cached_size_)*/{} + }; + _impl_.userid_.InitDefault(); + #ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + _impl_.userid_.Set("", GetArenaForAllocation()); + #endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING +} + +SyncProgressEvent::~SyncProgressEvent() { + // @@protoc_insertion_point(destructor:grpc.SyncProgressEvent) + if (auto *arena = _internal_metadata_.DeleteReturnArena<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>()) { + (void)arena; + return; + } + SharedDtor(); +} + +inline void SyncProgressEvent::SharedDtor() { + GOOGLE_DCHECK(GetArenaForAllocation() == nullptr); + _impl_.userid_.Destroy(); +} + +void SyncProgressEvent::SetCachedSize(int size) const { + _impl_._cached_size_.Set(size); +} + +void SyncProgressEvent::Clear() { +// @@protoc_insertion_point(message_clear_start:grpc.SyncProgressEvent) + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + _impl_.userid_.ClearToEmpty(); + ::memset(&_impl_.progress_, 0, static_cast( + reinterpret_cast(&_impl_.remainingms_) - + reinterpret_cast(&_impl_.progress_)) + sizeof(_impl_.remainingms_)); + _internal_metadata_.Clear<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(); +} + +const char* SyncProgressEvent::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) { +#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure + while (!ctx->Done(&ptr)) { + uint32_t tag; + ptr = ::_pbi::ReadTag(ptr, &tag); + switch (tag >> 3) { + // string userID = 1; + case 1: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 10)) { + auto str = _internal_mutable_userid(); + ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx); + CHK_(ptr); + CHK_(::_pbi::VerifyUTF8(str, "grpc.SyncProgressEvent.userID")); + } else + goto handle_unusual; + continue; + // double progress = 2; + case 2: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 17)) { + _impl_.progress_ = ::PROTOBUF_NAMESPACE_ID::internal::UnalignedLoad(ptr); + ptr += sizeof(double); + } else + goto handle_unusual; + continue; + // int64 elapsedMs = 3; + case 3: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 24)) { + _impl_.elapsedms_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + // int64 remainingMs = 4; + case 4: + if (PROTOBUF_PREDICT_TRUE(static_cast(tag) == 32)) { + _impl_.remainingms_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr); + CHK_(ptr); + } else + goto handle_unusual; + continue; + default: + goto handle_unusual; + } // switch + handle_unusual: + if ((tag == 0) || ((tag & 7) == 4)) { + CHK_(ptr); + ctx->SetLastTag(tag); + goto message_done; + } + ptr = UnknownFieldParse( + tag, + _internal_metadata_.mutable_unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(), + ptr, ctx); + CHK_(ptr != nullptr); + } // while +message_done: + return ptr; +failure: + ptr = nullptr; + goto message_done; +#undef CHK_ +} + +uint8_t* SyncProgressEvent::_InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const { + // @@protoc_insertion_point(serialize_to_array_start:grpc.SyncProgressEvent) + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + // string userID = 1; + if (!this->_internal_userid().empty()) { + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::VerifyUtf8String( + this->_internal_userid().data(), static_cast(this->_internal_userid().length()), + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::SERIALIZE, + "grpc.SyncProgressEvent.userID"); + target = stream->WriteStringMaybeAliased( + 1, this->_internal_userid(), target); + } + + // double progress = 2; + static_assert(sizeof(uint64_t) == sizeof(double), "Code assumes uint64_t and double are the same size."); + double tmp_progress = this->_internal_progress(); + uint64_t raw_progress; + memcpy(&raw_progress, &tmp_progress, sizeof(tmp_progress)); + if (raw_progress != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteDoubleToArray(2, this->_internal_progress(), target); + } + + // int64 elapsedMs = 3; + if (this->_internal_elapsedms() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteInt64ToArray(3, this->_internal_elapsedms(), target); + } + + // int64 remainingMs = 4; + if (this->_internal_remainingms() != 0) { + target = stream->EnsureSpace(target); + target = ::_pbi::WireFormatLite::WriteInt64ToArray(4, this->_internal_remainingms(), target); + } + + if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) { + target = ::_pbi::WireFormat::InternalSerializeUnknownFieldsToArray( + _internal_metadata_.unknown_fields<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(::PROTOBUF_NAMESPACE_ID::UnknownFieldSet::default_instance), target, stream); + } + // @@protoc_insertion_point(serialize_to_array_end:grpc.SyncProgressEvent) + return target; +} + +size_t SyncProgressEvent::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:grpc.SyncProgressEvent) + size_t total_size = 0; + + uint32_t cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + // string userID = 1; + if (!this->_internal_userid().empty()) { + total_size += 1 + + ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::StringSize( + this->_internal_userid()); + } + + // double progress = 2; + static_assert(sizeof(uint64_t) == sizeof(double), "Code assumes uint64_t and double are the same size."); + double tmp_progress = this->_internal_progress(); + uint64_t raw_progress; + memcpy(&raw_progress, &tmp_progress, sizeof(tmp_progress)); + if (raw_progress != 0) { + total_size += 1 + 8; + } + + // int64 elapsedMs = 3; + if (this->_internal_elapsedms() != 0) { + total_size += ::_pbi::WireFormatLite::Int64SizePlusOne(this->_internal_elapsedms()); + } + + // int64 remainingMs = 4; + if (this->_internal_remainingms() != 0) { + total_size += ::_pbi::WireFormatLite::Int64SizePlusOne(this->_internal_remainingms()); + } + + return MaybeComputeUnknownFieldsSize(total_size, &_impl_._cached_size_); +} + +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData SyncProgressEvent::_class_data_ = { + ::PROTOBUF_NAMESPACE_ID::Message::CopyWithSourceCheck, + SyncProgressEvent::MergeImpl +}; +const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*SyncProgressEvent::GetClassData() const { return &_class_data_; } + + +void SyncProgressEvent::MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg) { + auto* const _this = static_cast(&to_msg); + auto& from = static_cast(from_msg); + // @@protoc_insertion_point(class_specific_merge_from_start:grpc.SyncProgressEvent) + GOOGLE_DCHECK_NE(&from, _this); + uint32_t cached_has_bits = 0; + (void) cached_has_bits; + + if (!from._internal_userid().empty()) { + _this->_internal_set_userid(from._internal_userid()); + } + static_assert(sizeof(uint64_t) == sizeof(double), "Code assumes uint64_t and double are the same size."); + double tmp_progress = from._internal_progress(); + uint64_t raw_progress; + memcpy(&raw_progress, &tmp_progress, sizeof(tmp_progress)); + if (raw_progress != 0) { + _this->_internal_set_progress(from._internal_progress()); + } + if (from._internal_elapsedms() != 0) { + _this->_internal_set_elapsedms(from._internal_elapsedms()); + } + if (from._internal_remainingms() != 0) { + _this->_internal_set_remainingms(from._internal_remainingms()); + } + _this->_internal_metadata_.MergeFrom<::PROTOBUF_NAMESPACE_ID::UnknownFieldSet>(from._internal_metadata_); +} + +void SyncProgressEvent::CopyFrom(const SyncProgressEvent& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:grpc.SyncProgressEvent) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool SyncProgressEvent::IsInitialized() const { + return true; +} + +void SyncProgressEvent::InternalSwap(SyncProgressEvent* other) { + using std::swap; + auto* lhs_arena = GetArenaForAllocation(); + auto* rhs_arena = other->GetArenaForAllocation(); + _internal_metadata_.InternalSwap(&other->_internal_metadata_); + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr::InternalSwap( + &_impl_.userid_, lhs_arena, + &other->_impl_.userid_, rhs_arena + ); + ::PROTOBUF_NAMESPACE_ID::internal::memswap< + PROTOBUF_FIELD_OFFSET(SyncProgressEvent, _impl_.remainingms_) + + sizeof(SyncProgressEvent::_impl_.remainingms_) + - PROTOBUF_FIELD_OFFSET(SyncProgressEvent, _impl_.progress_)>( + reinterpret_cast(&_impl_.progress_), + reinterpret_cast(&other->_impl_.progress_)); +} + +::PROTOBUF_NAMESPACE_ID::Metadata SyncProgressEvent::GetMetadata() const { + return ::_pbi::AssignDescriptors( + &descriptor_table_bridge_2eproto_getter, &descriptor_table_bridge_2eproto_once, + file_level_metadata_bridge_2eproto[62]); +} + +// =================================================================== + class GenericErrorEvent::_Internal { public: }; @@ -14089,7 +15654,7 @@ void GenericErrorEvent::InternalSwap(GenericErrorEvent* other) { ::PROTOBUF_NAMESPACE_ID::Metadata GenericErrorEvent::GetMetadata() const { return ::_pbi::AssignDescriptors( &descriptor_table_bridge_2eproto_getter, &descriptor_table_bridge_2eproto_once, - file_level_metadata_bridge_2eproto[58]); + file_level_metadata_bridge_2eproto[63]); } // @@protoc_insertion_point(namespace_scope) @@ -14327,6 +15892,26 @@ template<> PROTOBUF_NOINLINE ::grpc::UserBadEvent* Arena::CreateMaybeMessage< ::grpc::UserBadEvent >(Arena* arena) { return Arena::CreateMessageInternal< ::grpc::UserBadEvent >(arena); } +template<> PROTOBUF_NOINLINE ::grpc::UsedBytesChangedEvent* +Arena::CreateMaybeMessage< ::grpc::UsedBytesChangedEvent >(Arena* arena) { + return Arena::CreateMessageInternal< ::grpc::UsedBytesChangedEvent >(arena); +} +template<> PROTOBUF_NOINLINE ::grpc::ImapLoginFailedEvent* +Arena::CreateMaybeMessage< ::grpc::ImapLoginFailedEvent >(Arena* arena) { + return Arena::CreateMessageInternal< ::grpc::ImapLoginFailedEvent >(arena); +} +template<> PROTOBUF_NOINLINE ::grpc::SyncStartedEvent* +Arena::CreateMaybeMessage< ::grpc::SyncStartedEvent >(Arena* arena) { + return Arena::CreateMessageInternal< ::grpc::SyncStartedEvent >(arena); +} +template<> PROTOBUF_NOINLINE ::grpc::SyncFinishedEvent* +Arena::CreateMaybeMessage< ::grpc::SyncFinishedEvent >(Arena* arena) { + return Arena::CreateMessageInternal< ::grpc::SyncFinishedEvent >(arena); +} +template<> PROTOBUF_NOINLINE ::grpc::SyncProgressEvent* +Arena::CreateMaybeMessage< ::grpc::SyncProgressEvent >(Arena* arena) { + return Arena::CreateMessageInternal< ::grpc::SyncProgressEvent >(arena); +} template<> PROTOBUF_NOINLINE ::grpc::GenericErrorEvent* Arena::CreateMaybeMessage< ::grpc::GenericErrorEvent >(Arena* arena) { return Arena::CreateMessageInternal< ::grpc::GenericErrorEvent >(arena); diff --git a/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/bridge.pb.h b/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/bridge.pb.h index 7a40aa01..ea2f44df 100644 --- a/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/bridge.pb.h +++ b/internal/frontend/bridge-gui/bridgepp/bridgepp/GRPC/bridge.pb.h @@ -100,6 +100,9 @@ extern GuiReadyResponseDefaultTypeInternal _GuiReadyResponse_default_instance_; class HasNoKeychainEvent; struct HasNoKeychainEventDefaultTypeInternal; extern HasNoKeychainEventDefaultTypeInternal _HasNoKeychainEvent_default_instance_; +class ImapLoginFailedEvent; +struct ImapLoginFailedEventDefaultTypeInternal; +extern ImapLoginFailedEventDefaultTypeInternal _ImapLoginFailedEvent_default_instance_; class ImapSmtpSettings; struct ImapSmtpSettingsDefaultTypeInternal; extern ImapSmtpSettingsDefaultTypeInternal _ImapSmtpSettings_default_instance_; @@ -169,6 +172,15 @@ extern ShowMainWindowEventDefaultTypeInternal _ShowMainWindowEvent_default_insta class StreamEvent; struct StreamEventDefaultTypeInternal; extern StreamEventDefaultTypeInternal _StreamEvent_default_instance_; +class SyncFinishedEvent; +struct SyncFinishedEventDefaultTypeInternal; +extern SyncFinishedEventDefaultTypeInternal _SyncFinishedEvent_default_instance_; +class SyncProgressEvent; +struct SyncProgressEventDefaultTypeInternal; +extern SyncProgressEventDefaultTypeInternal _SyncProgressEvent_default_instance_; +class SyncStartedEvent; +struct SyncStartedEventDefaultTypeInternal; +extern SyncStartedEventDefaultTypeInternal _SyncStartedEvent_default_instance_; class ToggleAutostartFinishedEvent; struct ToggleAutostartFinishedEventDefaultTypeInternal; extern ToggleAutostartFinishedEventDefaultTypeInternal _ToggleAutostartFinishedEvent_default_instance_; @@ -202,6 +214,9 @@ extern UpdateSilentRestartNeededDefaultTypeInternal _UpdateSilentRestartNeeded_d class UpdateVersionChanged; struct UpdateVersionChangedDefaultTypeInternal; extern UpdateVersionChangedDefaultTypeInternal _UpdateVersionChanged_default_instance_; +class UsedBytesChangedEvent; +struct UsedBytesChangedEventDefaultTypeInternal; +extern UsedBytesChangedEventDefaultTypeInternal _UsedBytesChangedEvent_default_instance_; class User; struct UserDefaultTypeInternal; extern UserDefaultTypeInternal _User_default_instance_; @@ -245,6 +260,7 @@ template<> ::grpc::EventStreamRequest* Arena::CreateMaybeMessage<::grpc::EventSt template<> ::grpc::GenericErrorEvent* Arena::CreateMaybeMessage<::grpc::GenericErrorEvent>(Arena*); template<> ::grpc::GuiReadyResponse* Arena::CreateMaybeMessage<::grpc::GuiReadyResponse>(Arena*); template<> ::grpc::HasNoKeychainEvent* Arena::CreateMaybeMessage<::grpc::HasNoKeychainEvent>(Arena*); +template<> ::grpc::ImapLoginFailedEvent* Arena::CreateMaybeMessage<::grpc::ImapLoginFailedEvent>(Arena*); template<> ::grpc::ImapSmtpSettings* Arena::CreateMaybeMessage<::grpc::ImapSmtpSettings>(Arena*); template<> ::grpc::InternetStatusEvent* Arena::CreateMaybeMessage<::grpc::InternetStatusEvent>(Arena*); template<> ::grpc::KeychainEvent* Arena::CreateMaybeMessage<::grpc::KeychainEvent>(Arena*); @@ -268,6 +284,9 @@ template<> ::grpc::ReportBugSuccessEvent* Arena::CreateMaybeMessage<::grpc::Repo template<> ::grpc::ResetFinishedEvent* Arena::CreateMaybeMessage<::grpc::ResetFinishedEvent>(Arena*); template<> ::grpc::ShowMainWindowEvent* Arena::CreateMaybeMessage<::grpc::ShowMainWindowEvent>(Arena*); template<> ::grpc::StreamEvent* Arena::CreateMaybeMessage<::grpc::StreamEvent>(Arena*); +template<> ::grpc::SyncFinishedEvent* Arena::CreateMaybeMessage<::grpc::SyncFinishedEvent>(Arena*); +template<> ::grpc::SyncProgressEvent* Arena::CreateMaybeMessage<::grpc::SyncProgressEvent>(Arena*); +template<> ::grpc::SyncStartedEvent* Arena::CreateMaybeMessage<::grpc::SyncStartedEvent>(Arena*); template<> ::grpc::ToggleAutostartFinishedEvent* Arena::CreateMaybeMessage<::grpc::ToggleAutostartFinishedEvent>(Arena*); template<> ::grpc::ToggleSplitModeFinishedEvent* Arena::CreateMaybeMessage<::grpc::ToggleSplitModeFinishedEvent>(Arena*); template<> ::grpc::UpdateCheckFinished* Arena::CreateMaybeMessage<::grpc::UpdateCheckFinished>(Arena*); @@ -279,6 +298,7 @@ template<> ::grpc::UpdateManualReadyEvent* Arena::CreateMaybeMessage<::grpc::Upd template<> ::grpc::UpdateManualRestartNeededEvent* Arena::CreateMaybeMessage<::grpc::UpdateManualRestartNeededEvent>(Arena*); template<> ::grpc::UpdateSilentRestartNeeded* Arena::CreateMaybeMessage<::grpc::UpdateSilentRestartNeeded>(Arena*); template<> ::grpc::UpdateVersionChanged* Arena::CreateMaybeMessage<::grpc::UpdateVersionChanged>(Arena*); +template<> ::grpc::UsedBytesChangedEvent* Arena::CreateMaybeMessage<::grpc::UsedBytesChangedEvent>(Arena*); template<> ::grpc::User* Arena::CreateMaybeMessage<::grpc::User>(Arena*); template<> ::grpc::UserBadEvent* Arena::CreateMaybeMessage<::grpc::UserBadEvent>(Arena*); template<> ::grpc::UserBadEventFeedbackRequest* Arena::CreateMaybeMessage<::grpc::UserBadEventFeedbackRequest>(Arena*); @@ -9245,6 +9265,11 @@ class UserEvent final : kUserDisconnected = 2, kUserChanged = 3, kUserBadEvent = 4, + kUsedBytesChangedEvent = 5, + kImapLoginFailedEvent = 6, + kSyncStartedEvent = 7, + kSyncFinishedEvent = 8, + kSyncProgressEvent = 9, EVENT_NOT_SET = 0, }; @@ -9330,6 +9355,11 @@ class UserEvent final : kUserDisconnectedFieldNumber = 2, kUserChangedFieldNumber = 3, kUserBadEventFieldNumber = 4, + kUsedBytesChangedEventFieldNumber = 5, + kImapLoginFailedEventFieldNumber = 6, + kSyncStartedEventFieldNumber = 7, + kSyncFinishedEventFieldNumber = 8, + kSyncProgressEventFieldNumber = 9, }; // .grpc.ToggleSplitModeFinishedEvent toggleSplitModeFinished = 1; bool has_togglesplitmodefinished() const; @@ -9403,6 +9433,96 @@ class UserEvent final : ::grpc::UserBadEvent* userbadevent); ::grpc::UserBadEvent* unsafe_arena_release_userbadevent(); + // .grpc.UsedBytesChangedEvent usedBytesChangedEvent = 5; + bool has_usedbyteschangedevent() const; + private: + bool _internal_has_usedbyteschangedevent() const; + public: + void clear_usedbyteschangedevent(); + const ::grpc::UsedBytesChangedEvent& usedbyteschangedevent() const; + PROTOBUF_NODISCARD ::grpc::UsedBytesChangedEvent* release_usedbyteschangedevent(); + ::grpc::UsedBytesChangedEvent* mutable_usedbyteschangedevent(); + void set_allocated_usedbyteschangedevent(::grpc::UsedBytesChangedEvent* usedbyteschangedevent); + private: + const ::grpc::UsedBytesChangedEvent& _internal_usedbyteschangedevent() const; + ::grpc::UsedBytesChangedEvent* _internal_mutable_usedbyteschangedevent(); + public: + void unsafe_arena_set_allocated_usedbyteschangedevent( + ::grpc::UsedBytesChangedEvent* usedbyteschangedevent); + ::grpc::UsedBytesChangedEvent* unsafe_arena_release_usedbyteschangedevent(); + + // .grpc.ImapLoginFailedEvent imapLoginFailedEvent = 6; + bool has_imaploginfailedevent() const; + private: + bool _internal_has_imaploginfailedevent() const; + public: + void clear_imaploginfailedevent(); + const ::grpc::ImapLoginFailedEvent& imaploginfailedevent() const; + PROTOBUF_NODISCARD ::grpc::ImapLoginFailedEvent* release_imaploginfailedevent(); + ::grpc::ImapLoginFailedEvent* mutable_imaploginfailedevent(); + void set_allocated_imaploginfailedevent(::grpc::ImapLoginFailedEvent* imaploginfailedevent); + private: + const ::grpc::ImapLoginFailedEvent& _internal_imaploginfailedevent() const; + ::grpc::ImapLoginFailedEvent* _internal_mutable_imaploginfailedevent(); + public: + void unsafe_arena_set_allocated_imaploginfailedevent( + ::grpc::ImapLoginFailedEvent* imaploginfailedevent); + ::grpc::ImapLoginFailedEvent* unsafe_arena_release_imaploginfailedevent(); + + // .grpc.SyncStartedEvent syncStartedEvent = 7; + bool has_syncstartedevent() const; + private: + bool _internal_has_syncstartedevent() const; + public: + void clear_syncstartedevent(); + const ::grpc::SyncStartedEvent& syncstartedevent() const; + PROTOBUF_NODISCARD ::grpc::SyncStartedEvent* release_syncstartedevent(); + ::grpc::SyncStartedEvent* mutable_syncstartedevent(); + void set_allocated_syncstartedevent(::grpc::SyncStartedEvent* syncstartedevent); + private: + const ::grpc::SyncStartedEvent& _internal_syncstartedevent() const; + ::grpc::SyncStartedEvent* _internal_mutable_syncstartedevent(); + public: + void unsafe_arena_set_allocated_syncstartedevent( + ::grpc::SyncStartedEvent* syncstartedevent); + ::grpc::SyncStartedEvent* unsafe_arena_release_syncstartedevent(); + + // .grpc.SyncFinishedEvent syncFinishedEvent = 8; + bool has_syncfinishedevent() const; + private: + bool _internal_has_syncfinishedevent() const; + public: + void clear_syncfinishedevent(); + const ::grpc::SyncFinishedEvent& syncfinishedevent() const; + PROTOBUF_NODISCARD ::grpc::SyncFinishedEvent* release_syncfinishedevent(); + ::grpc::SyncFinishedEvent* mutable_syncfinishedevent(); + void set_allocated_syncfinishedevent(::grpc::SyncFinishedEvent* syncfinishedevent); + private: + const ::grpc::SyncFinishedEvent& _internal_syncfinishedevent() const; + ::grpc::SyncFinishedEvent* _internal_mutable_syncfinishedevent(); + public: + void unsafe_arena_set_allocated_syncfinishedevent( + ::grpc::SyncFinishedEvent* syncfinishedevent); + ::grpc::SyncFinishedEvent* unsafe_arena_release_syncfinishedevent(); + + // .grpc.SyncProgressEvent syncProgressEvent = 9; + bool has_syncprogressevent() const; + private: + bool _internal_has_syncprogressevent() const; + public: + void clear_syncprogressevent(); + const ::grpc::SyncProgressEvent& syncprogressevent() const; + PROTOBUF_NODISCARD ::grpc::SyncProgressEvent* release_syncprogressevent(); + ::grpc::SyncProgressEvent* mutable_syncprogressevent(); + void set_allocated_syncprogressevent(::grpc::SyncProgressEvent* syncprogressevent); + private: + const ::grpc::SyncProgressEvent& _internal_syncprogressevent() const; + ::grpc::SyncProgressEvent* _internal_mutable_syncprogressevent(); + public: + void unsafe_arena_set_allocated_syncprogressevent( + ::grpc::SyncProgressEvent* syncprogressevent); + ::grpc::SyncProgressEvent* unsafe_arena_release_syncprogressevent(); + void clear_event(); EventCase event_case() const; // @@protoc_insertion_point(class_scope:grpc.UserEvent) @@ -9412,6 +9532,11 @@ class UserEvent final : void set_has_userdisconnected(); void set_has_userchanged(); void set_has_userbadevent(); + void set_has_usedbyteschangedevent(); + void set_has_imaploginfailedevent(); + void set_has_syncstartedevent(); + void set_has_syncfinishedevent(); + void set_has_syncprogressevent(); inline bool has_event() const; inline void clear_has_event(); @@ -9427,6 +9552,11 @@ class UserEvent final : ::grpc::UserDisconnectedEvent* userdisconnected_; ::grpc::UserChangedEvent* userchanged_; ::grpc::UserBadEvent* userbadevent_; + ::grpc::UsedBytesChangedEvent* usedbyteschangedevent_; + ::grpc::ImapLoginFailedEvent* imaploginfailedevent_; + ::grpc::SyncStartedEvent* syncstartedevent_; + ::grpc::SyncFinishedEvent* syncfinishedevent_; + ::grpc::SyncProgressEvent* syncprogressevent_; } event_; mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; uint32_t _oneof_case_[1]; @@ -10065,6 +10195,815 @@ class UserBadEvent final : }; // ------------------------------------------------------------------- +class UsedBytesChangedEvent final : + public ::PROTOBUF_NAMESPACE_ID::Message /* @@protoc_insertion_point(class_definition:grpc.UsedBytesChangedEvent) */ { + public: + inline UsedBytesChangedEvent() : UsedBytesChangedEvent(nullptr) {} + ~UsedBytesChangedEvent() override; + explicit PROTOBUF_CONSTEXPR UsedBytesChangedEvent(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + UsedBytesChangedEvent(const UsedBytesChangedEvent& from); + UsedBytesChangedEvent(UsedBytesChangedEvent&& from) noexcept + : UsedBytesChangedEvent() { + *this = ::std::move(from); + } + + inline UsedBytesChangedEvent& operator=(const UsedBytesChangedEvent& from) { + CopyFrom(from); + return *this; + } + inline UsedBytesChangedEvent& operator=(UsedBytesChangedEvent&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* descriptor() { + return GetDescriptor(); + } + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* GetDescriptor() { + return default_instance().GetMetadata().descriptor; + } + static const ::PROTOBUF_NAMESPACE_ID::Reflection* GetReflection() { + return default_instance().GetMetadata().reflection; + } + static const UsedBytesChangedEvent& default_instance() { + return *internal_default_instance(); + } + static inline const UsedBytesChangedEvent* internal_default_instance() { + return reinterpret_cast( + &_UsedBytesChangedEvent_default_instance_); + } + static constexpr int kIndexInFileMessages = + 58; + + friend void swap(UsedBytesChangedEvent& a, UsedBytesChangedEvent& b) { + a.Swap(&b); + } + inline void Swap(UsedBytesChangedEvent* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(UsedBytesChangedEvent* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + UsedBytesChangedEvent* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage(arena); + } + using ::PROTOBUF_NAMESPACE_ID::Message::CopyFrom; + void CopyFrom(const UsedBytesChangedEvent& from); + using ::PROTOBUF_NAMESPACE_ID::Message::MergeFrom; + void MergeFrom( const UsedBytesChangedEvent& from) { + UsedBytesChangedEvent::MergeImpl(*this, from); + } + private: + static void MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg); + public: + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const final; + void InternalSwap(UsedBytesChangedEvent* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "grpc.UsedBytesChangedEvent"; + } + protected: + explicit UsedBytesChangedEvent(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + static const ClassData _class_data_; + const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*GetClassData() const final; + + ::PROTOBUF_NAMESPACE_ID::Metadata GetMetadata() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kUserIDFieldNumber = 1, + kUsedBytesFieldNumber = 2, + }; + // string userID = 1; + void clear_userid(); + const std::string& userid() const; + template + void set_userid(ArgT0&& arg0, ArgT... args); + std::string* mutable_userid(); + PROTOBUF_NODISCARD std::string* release_userid(); + void set_allocated_userid(std::string* userid); + private: + const std::string& _internal_userid() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_userid(const std::string& value); + std::string* _internal_mutable_userid(); + public: + + // int64 usedBytes = 2; + void clear_usedbytes(); + int64_t usedbytes() const; + void set_usedbytes(int64_t value); + private: + int64_t _internal_usedbytes() const; + void _internal_set_usedbytes(int64_t value); + public: + + // @@protoc_insertion_point(class_scope:grpc.UsedBytesChangedEvent) + private: + class _Internal; + + template friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr userid_; + int64_t usedbytes_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_bridge_2eproto; +}; +// ------------------------------------------------------------------- + +class ImapLoginFailedEvent final : + public ::PROTOBUF_NAMESPACE_ID::Message /* @@protoc_insertion_point(class_definition:grpc.ImapLoginFailedEvent) */ { + public: + inline ImapLoginFailedEvent() : ImapLoginFailedEvent(nullptr) {} + ~ImapLoginFailedEvent() override; + explicit PROTOBUF_CONSTEXPR ImapLoginFailedEvent(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + ImapLoginFailedEvent(const ImapLoginFailedEvent& from); + ImapLoginFailedEvent(ImapLoginFailedEvent&& from) noexcept + : ImapLoginFailedEvent() { + *this = ::std::move(from); + } + + inline ImapLoginFailedEvent& operator=(const ImapLoginFailedEvent& from) { + CopyFrom(from); + return *this; + } + inline ImapLoginFailedEvent& operator=(ImapLoginFailedEvent&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* descriptor() { + return GetDescriptor(); + } + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* GetDescriptor() { + return default_instance().GetMetadata().descriptor; + } + static const ::PROTOBUF_NAMESPACE_ID::Reflection* GetReflection() { + return default_instance().GetMetadata().reflection; + } + static const ImapLoginFailedEvent& default_instance() { + return *internal_default_instance(); + } + static inline const ImapLoginFailedEvent* internal_default_instance() { + return reinterpret_cast( + &_ImapLoginFailedEvent_default_instance_); + } + static constexpr int kIndexInFileMessages = + 59; + + friend void swap(ImapLoginFailedEvent& a, ImapLoginFailedEvent& b) { + a.Swap(&b); + } + inline void Swap(ImapLoginFailedEvent* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(ImapLoginFailedEvent* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + ImapLoginFailedEvent* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage(arena); + } + using ::PROTOBUF_NAMESPACE_ID::Message::CopyFrom; + void CopyFrom(const ImapLoginFailedEvent& from); + using ::PROTOBUF_NAMESPACE_ID::Message::MergeFrom; + void MergeFrom( const ImapLoginFailedEvent& from) { + ImapLoginFailedEvent::MergeImpl(*this, from); + } + private: + static void MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg); + public: + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const final; + void InternalSwap(ImapLoginFailedEvent* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "grpc.ImapLoginFailedEvent"; + } + protected: + explicit ImapLoginFailedEvent(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + static const ClassData _class_data_; + const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*GetClassData() const final; + + ::PROTOBUF_NAMESPACE_ID::Metadata GetMetadata() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kUsernameFieldNumber = 1, + }; + // string username = 1; + void clear_username(); + const std::string& username() const; + template + void set_username(ArgT0&& arg0, ArgT... args); + std::string* mutable_username(); + PROTOBUF_NODISCARD std::string* release_username(); + void set_allocated_username(std::string* username); + private: + const std::string& _internal_username() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_username(const std::string& value); + std::string* _internal_mutable_username(); + public: + + // @@protoc_insertion_point(class_scope:grpc.ImapLoginFailedEvent) + private: + class _Internal; + + template friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr username_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_bridge_2eproto; +}; +// ------------------------------------------------------------------- + +class SyncStartedEvent final : + public ::PROTOBUF_NAMESPACE_ID::Message /* @@protoc_insertion_point(class_definition:grpc.SyncStartedEvent) */ { + public: + inline SyncStartedEvent() : SyncStartedEvent(nullptr) {} + ~SyncStartedEvent() override; + explicit PROTOBUF_CONSTEXPR SyncStartedEvent(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + SyncStartedEvent(const SyncStartedEvent& from); + SyncStartedEvent(SyncStartedEvent&& from) noexcept + : SyncStartedEvent() { + *this = ::std::move(from); + } + + inline SyncStartedEvent& operator=(const SyncStartedEvent& from) { + CopyFrom(from); + return *this; + } + inline SyncStartedEvent& operator=(SyncStartedEvent&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* descriptor() { + return GetDescriptor(); + } + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* GetDescriptor() { + return default_instance().GetMetadata().descriptor; + } + static const ::PROTOBUF_NAMESPACE_ID::Reflection* GetReflection() { + return default_instance().GetMetadata().reflection; + } + static const SyncStartedEvent& default_instance() { + return *internal_default_instance(); + } + static inline const SyncStartedEvent* internal_default_instance() { + return reinterpret_cast( + &_SyncStartedEvent_default_instance_); + } + static constexpr int kIndexInFileMessages = + 60; + + friend void swap(SyncStartedEvent& a, SyncStartedEvent& b) { + a.Swap(&b); + } + inline void Swap(SyncStartedEvent* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(SyncStartedEvent* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + SyncStartedEvent* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage(arena); + } + using ::PROTOBUF_NAMESPACE_ID::Message::CopyFrom; + void CopyFrom(const SyncStartedEvent& from); + using ::PROTOBUF_NAMESPACE_ID::Message::MergeFrom; + void MergeFrom( const SyncStartedEvent& from) { + SyncStartedEvent::MergeImpl(*this, from); + } + private: + static void MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg); + public: + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const final; + void InternalSwap(SyncStartedEvent* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "grpc.SyncStartedEvent"; + } + protected: + explicit SyncStartedEvent(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + static const ClassData _class_data_; + const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*GetClassData() const final; + + ::PROTOBUF_NAMESPACE_ID::Metadata GetMetadata() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kUserIDFieldNumber = 1, + }; + // string userID = 1; + void clear_userid(); + const std::string& userid() const; + template + void set_userid(ArgT0&& arg0, ArgT... args); + std::string* mutable_userid(); + PROTOBUF_NODISCARD std::string* release_userid(); + void set_allocated_userid(std::string* userid); + private: + const std::string& _internal_userid() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_userid(const std::string& value); + std::string* _internal_mutable_userid(); + public: + + // @@protoc_insertion_point(class_scope:grpc.SyncStartedEvent) + private: + class _Internal; + + template friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr userid_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_bridge_2eproto; +}; +// ------------------------------------------------------------------- + +class SyncFinishedEvent final : + public ::PROTOBUF_NAMESPACE_ID::Message /* @@protoc_insertion_point(class_definition:grpc.SyncFinishedEvent) */ { + public: + inline SyncFinishedEvent() : SyncFinishedEvent(nullptr) {} + ~SyncFinishedEvent() override; + explicit PROTOBUF_CONSTEXPR SyncFinishedEvent(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + SyncFinishedEvent(const SyncFinishedEvent& from); + SyncFinishedEvent(SyncFinishedEvent&& from) noexcept + : SyncFinishedEvent() { + *this = ::std::move(from); + } + + inline SyncFinishedEvent& operator=(const SyncFinishedEvent& from) { + CopyFrom(from); + return *this; + } + inline SyncFinishedEvent& operator=(SyncFinishedEvent&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* descriptor() { + return GetDescriptor(); + } + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* GetDescriptor() { + return default_instance().GetMetadata().descriptor; + } + static const ::PROTOBUF_NAMESPACE_ID::Reflection* GetReflection() { + return default_instance().GetMetadata().reflection; + } + static const SyncFinishedEvent& default_instance() { + return *internal_default_instance(); + } + static inline const SyncFinishedEvent* internal_default_instance() { + return reinterpret_cast( + &_SyncFinishedEvent_default_instance_); + } + static constexpr int kIndexInFileMessages = + 61; + + friend void swap(SyncFinishedEvent& a, SyncFinishedEvent& b) { + a.Swap(&b); + } + inline void Swap(SyncFinishedEvent* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(SyncFinishedEvent* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + SyncFinishedEvent* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage(arena); + } + using ::PROTOBUF_NAMESPACE_ID::Message::CopyFrom; + void CopyFrom(const SyncFinishedEvent& from); + using ::PROTOBUF_NAMESPACE_ID::Message::MergeFrom; + void MergeFrom( const SyncFinishedEvent& from) { + SyncFinishedEvent::MergeImpl(*this, from); + } + private: + static void MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg); + public: + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const final; + void InternalSwap(SyncFinishedEvent* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "grpc.SyncFinishedEvent"; + } + protected: + explicit SyncFinishedEvent(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + static const ClassData _class_data_; + const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*GetClassData() const final; + + ::PROTOBUF_NAMESPACE_ID::Metadata GetMetadata() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kUserIDFieldNumber = 1, + }; + // string userID = 1; + void clear_userid(); + const std::string& userid() const; + template + void set_userid(ArgT0&& arg0, ArgT... args); + std::string* mutable_userid(); + PROTOBUF_NODISCARD std::string* release_userid(); + void set_allocated_userid(std::string* userid); + private: + const std::string& _internal_userid() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_userid(const std::string& value); + std::string* _internal_mutable_userid(); + public: + + // @@protoc_insertion_point(class_scope:grpc.SyncFinishedEvent) + private: + class _Internal; + + template friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr userid_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_bridge_2eproto; +}; +// ------------------------------------------------------------------- + +class SyncProgressEvent final : + public ::PROTOBUF_NAMESPACE_ID::Message /* @@protoc_insertion_point(class_definition:grpc.SyncProgressEvent) */ { + public: + inline SyncProgressEvent() : SyncProgressEvent(nullptr) {} + ~SyncProgressEvent() override; + explicit PROTOBUF_CONSTEXPR SyncProgressEvent(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized); + + SyncProgressEvent(const SyncProgressEvent& from); + SyncProgressEvent(SyncProgressEvent&& from) noexcept + : SyncProgressEvent() { + *this = ::std::move(from); + } + + inline SyncProgressEvent& operator=(const SyncProgressEvent& from) { + CopyFrom(from); + return *this; + } + inline SyncProgressEvent& operator=(SyncProgressEvent&& from) noexcept { + if (this == &from) return *this; + if (GetOwningArena() == from.GetOwningArena() + #ifdef PROTOBUF_FORCE_COPY_IN_MOVE + && GetOwningArena() != nullptr + #endif // !PROTOBUF_FORCE_COPY_IN_MOVE + ) { + InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* descriptor() { + return GetDescriptor(); + } + static const ::PROTOBUF_NAMESPACE_ID::Descriptor* GetDescriptor() { + return default_instance().GetMetadata().descriptor; + } + static const ::PROTOBUF_NAMESPACE_ID::Reflection* GetReflection() { + return default_instance().GetMetadata().reflection; + } + static const SyncProgressEvent& default_instance() { + return *internal_default_instance(); + } + static inline const SyncProgressEvent* internal_default_instance() { + return reinterpret_cast( + &_SyncProgressEvent_default_instance_); + } + static constexpr int kIndexInFileMessages = + 62; + + friend void swap(SyncProgressEvent& a, SyncProgressEvent& b) { + a.Swap(&b); + } + inline void Swap(SyncProgressEvent* other) { + if (other == this) return; + #ifdef PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() != nullptr && + GetOwningArena() == other->GetOwningArena()) { + #else // PROTOBUF_FORCE_COPY_IN_SWAP + if (GetOwningArena() == other->GetOwningArena()) { + #endif // !PROTOBUF_FORCE_COPY_IN_SWAP + InternalSwap(other); + } else { + ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other); + } + } + void UnsafeArenaSwap(SyncProgressEvent* other) { + if (other == this) return; + GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena()); + InternalSwap(other); + } + + // implements Message ---------------------------------------------- + + SyncProgressEvent* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final { + return CreateMaybeMessage(arena); + } + using ::PROTOBUF_NAMESPACE_ID::Message::CopyFrom; + void CopyFrom(const SyncProgressEvent& from); + using ::PROTOBUF_NAMESPACE_ID::Message::MergeFrom; + void MergeFrom( const SyncProgressEvent& from) { + SyncProgressEvent::MergeImpl(*this, from); + } + private: + static void MergeImpl(::PROTOBUF_NAMESPACE_ID::Message& to_msg, const ::PROTOBUF_NAMESPACE_ID::Message& from_msg); + public: + PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final; + bool IsInitialized() const final; + + size_t ByteSizeLong() const final; + const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final; + uint8_t* _InternalSerialize( + uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final; + int GetCachedSize() const final { return _impl_._cached_size_.Get(); } + + private: + void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned); + void SharedDtor(); + void SetCachedSize(int size) const final; + void InternalSwap(SyncProgressEvent* other); + + private: + friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata; + static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() { + return "grpc.SyncProgressEvent"; + } + protected: + explicit SyncProgressEvent(::PROTOBUF_NAMESPACE_ID::Arena* arena, + bool is_message_owned = false); + public: + + static const ClassData _class_data_; + const ::PROTOBUF_NAMESPACE_ID::Message::ClassData*GetClassData() const final; + + ::PROTOBUF_NAMESPACE_ID::Metadata GetMetadata() const final; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + enum : int { + kUserIDFieldNumber = 1, + kProgressFieldNumber = 2, + kElapsedMsFieldNumber = 3, + kRemainingMsFieldNumber = 4, + }; + // string userID = 1; + void clear_userid(); + const std::string& userid() const; + template + void set_userid(ArgT0&& arg0, ArgT... args); + std::string* mutable_userid(); + PROTOBUF_NODISCARD std::string* release_userid(); + void set_allocated_userid(std::string* userid); + private: + const std::string& _internal_userid() const; + inline PROTOBUF_ALWAYS_INLINE void _internal_set_userid(const std::string& value); + std::string* _internal_mutable_userid(); + public: + + // double progress = 2; + void clear_progress(); + double progress() const; + void set_progress(double value); + private: + double _internal_progress() const; + void _internal_set_progress(double value); + public: + + // int64 elapsedMs = 3; + void clear_elapsedms(); + int64_t elapsedms() const; + void set_elapsedms(int64_t value); + private: + int64_t _internal_elapsedms() const; + void _internal_set_elapsedms(int64_t value); + public: + + // int64 remainingMs = 4; + void clear_remainingms(); + int64_t remainingms() const; + void set_remainingms(int64_t value); + private: + int64_t _internal_remainingms() const; + void _internal_set_remainingms(int64_t value); + public: + + // @@protoc_insertion_point(class_scope:grpc.SyncProgressEvent) + private: + class _Internal; + + template friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper; + typedef void InternalArenaConstructable_; + typedef void DestructorSkippable_; + struct Impl_ { + ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr userid_; + double progress_; + int64_t elapsedms_; + int64_t remainingms_; + mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_; + }; + union { Impl_ _impl_; }; + friend struct ::TableStruct_bridge_2eproto; +}; +// ------------------------------------------------------------------- + class GenericErrorEvent final : public ::PROTOBUF_NAMESPACE_ID::Message /* @@protoc_insertion_point(class_definition:grpc.GenericErrorEvent) */ { public: @@ -10113,7 +11052,7 @@ class GenericErrorEvent final : &_GenericErrorEvent_default_instance_); } static constexpr int kIndexInFileMessages = - 58; + 63; friend void swap(GenericErrorEvent& a, GenericErrorEvent& b) { a.Swap(&b); @@ -15970,6 +16909,376 @@ inline ::grpc::UserBadEvent* UserEvent::mutable_userbadevent() { return _msg; } +// .grpc.UsedBytesChangedEvent usedBytesChangedEvent = 5; +inline bool UserEvent::_internal_has_usedbyteschangedevent() const { + return event_case() == kUsedBytesChangedEvent; +} +inline bool UserEvent::has_usedbyteschangedevent() const { + return _internal_has_usedbyteschangedevent(); +} +inline void UserEvent::set_has_usedbyteschangedevent() { + _impl_._oneof_case_[0] = kUsedBytesChangedEvent; +} +inline void UserEvent::clear_usedbyteschangedevent() { + if (_internal_has_usedbyteschangedevent()) { + if (GetArenaForAllocation() == nullptr) { + delete _impl_.event_.usedbyteschangedevent_; + } + clear_has_event(); + } +} +inline ::grpc::UsedBytesChangedEvent* UserEvent::release_usedbyteschangedevent() { + // @@protoc_insertion_point(field_release:grpc.UserEvent.usedBytesChangedEvent) + if (_internal_has_usedbyteschangedevent()) { + clear_has_event(); + ::grpc::UsedBytesChangedEvent* temp = _impl_.event_.usedbyteschangedevent_; + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } + _impl_.event_.usedbyteschangedevent_ = nullptr; + return temp; + } else { + return nullptr; + } +} +inline const ::grpc::UsedBytesChangedEvent& UserEvent::_internal_usedbyteschangedevent() const { + return _internal_has_usedbyteschangedevent() + ? *_impl_.event_.usedbyteschangedevent_ + : reinterpret_cast< ::grpc::UsedBytesChangedEvent&>(::grpc::_UsedBytesChangedEvent_default_instance_); +} +inline const ::grpc::UsedBytesChangedEvent& UserEvent::usedbyteschangedevent() const { + // @@protoc_insertion_point(field_get:grpc.UserEvent.usedBytesChangedEvent) + return _internal_usedbyteschangedevent(); +} +inline ::grpc::UsedBytesChangedEvent* UserEvent::unsafe_arena_release_usedbyteschangedevent() { + // @@protoc_insertion_point(field_unsafe_arena_release:grpc.UserEvent.usedBytesChangedEvent) + if (_internal_has_usedbyteschangedevent()) { + clear_has_event(); + ::grpc::UsedBytesChangedEvent* temp = _impl_.event_.usedbyteschangedevent_; + _impl_.event_.usedbyteschangedevent_ = nullptr; + return temp; + } else { + return nullptr; + } +} +inline void UserEvent::unsafe_arena_set_allocated_usedbyteschangedevent(::grpc::UsedBytesChangedEvent* usedbyteschangedevent) { + clear_event(); + if (usedbyteschangedevent) { + set_has_usedbyteschangedevent(); + _impl_.event_.usedbyteschangedevent_ = usedbyteschangedevent; + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:grpc.UserEvent.usedBytesChangedEvent) +} +inline ::grpc::UsedBytesChangedEvent* UserEvent::_internal_mutable_usedbyteschangedevent() { + if (!_internal_has_usedbyteschangedevent()) { + clear_event(); + set_has_usedbyteschangedevent(); + _impl_.event_.usedbyteschangedevent_ = CreateMaybeMessage< ::grpc::UsedBytesChangedEvent >(GetArenaForAllocation()); + } + return _impl_.event_.usedbyteschangedevent_; +} +inline ::grpc::UsedBytesChangedEvent* UserEvent::mutable_usedbyteschangedevent() { + ::grpc::UsedBytesChangedEvent* _msg = _internal_mutable_usedbyteschangedevent(); + // @@protoc_insertion_point(field_mutable:grpc.UserEvent.usedBytesChangedEvent) + return _msg; +} + +// .grpc.ImapLoginFailedEvent imapLoginFailedEvent = 6; +inline bool UserEvent::_internal_has_imaploginfailedevent() const { + return event_case() == kImapLoginFailedEvent; +} +inline bool UserEvent::has_imaploginfailedevent() const { + return _internal_has_imaploginfailedevent(); +} +inline void UserEvent::set_has_imaploginfailedevent() { + _impl_._oneof_case_[0] = kImapLoginFailedEvent; +} +inline void UserEvent::clear_imaploginfailedevent() { + if (_internal_has_imaploginfailedevent()) { + if (GetArenaForAllocation() == nullptr) { + delete _impl_.event_.imaploginfailedevent_; + } + clear_has_event(); + } +} +inline ::grpc::ImapLoginFailedEvent* UserEvent::release_imaploginfailedevent() { + // @@protoc_insertion_point(field_release:grpc.UserEvent.imapLoginFailedEvent) + if (_internal_has_imaploginfailedevent()) { + clear_has_event(); + ::grpc::ImapLoginFailedEvent* temp = _impl_.event_.imaploginfailedevent_; + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } + _impl_.event_.imaploginfailedevent_ = nullptr; + return temp; + } else { + return nullptr; + } +} +inline const ::grpc::ImapLoginFailedEvent& UserEvent::_internal_imaploginfailedevent() const { + return _internal_has_imaploginfailedevent() + ? *_impl_.event_.imaploginfailedevent_ + : reinterpret_cast< ::grpc::ImapLoginFailedEvent&>(::grpc::_ImapLoginFailedEvent_default_instance_); +} +inline const ::grpc::ImapLoginFailedEvent& UserEvent::imaploginfailedevent() const { + // @@protoc_insertion_point(field_get:grpc.UserEvent.imapLoginFailedEvent) + return _internal_imaploginfailedevent(); +} +inline ::grpc::ImapLoginFailedEvent* UserEvent::unsafe_arena_release_imaploginfailedevent() { + // @@protoc_insertion_point(field_unsafe_arena_release:grpc.UserEvent.imapLoginFailedEvent) + if (_internal_has_imaploginfailedevent()) { + clear_has_event(); + ::grpc::ImapLoginFailedEvent* temp = _impl_.event_.imaploginfailedevent_; + _impl_.event_.imaploginfailedevent_ = nullptr; + return temp; + } else { + return nullptr; + } +} +inline void UserEvent::unsafe_arena_set_allocated_imaploginfailedevent(::grpc::ImapLoginFailedEvent* imaploginfailedevent) { + clear_event(); + if (imaploginfailedevent) { + set_has_imaploginfailedevent(); + _impl_.event_.imaploginfailedevent_ = imaploginfailedevent; + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:grpc.UserEvent.imapLoginFailedEvent) +} +inline ::grpc::ImapLoginFailedEvent* UserEvent::_internal_mutable_imaploginfailedevent() { + if (!_internal_has_imaploginfailedevent()) { + clear_event(); + set_has_imaploginfailedevent(); + _impl_.event_.imaploginfailedevent_ = CreateMaybeMessage< ::grpc::ImapLoginFailedEvent >(GetArenaForAllocation()); + } + return _impl_.event_.imaploginfailedevent_; +} +inline ::grpc::ImapLoginFailedEvent* UserEvent::mutable_imaploginfailedevent() { + ::grpc::ImapLoginFailedEvent* _msg = _internal_mutable_imaploginfailedevent(); + // @@protoc_insertion_point(field_mutable:grpc.UserEvent.imapLoginFailedEvent) + return _msg; +} + +// .grpc.SyncStartedEvent syncStartedEvent = 7; +inline bool UserEvent::_internal_has_syncstartedevent() const { + return event_case() == kSyncStartedEvent; +} +inline bool UserEvent::has_syncstartedevent() const { + return _internal_has_syncstartedevent(); +} +inline void UserEvent::set_has_syncstartedevent() { + _impl_._oneof_case_[0] = kSyncStartedEvent; +} +inline void UserEvent::clear_syncstartedevent() { + if (_internal_has_syncstartedevent()) { + if (GetArenaForAllocation() == nullptr) { + delete _impl_.event_.syncstartedevent_; + } + clear_has_event(); + } +} +inline ::grpc::SyncStartedEvent* UserEvent::release_syncstartedevent() { + // @@protoc_insertion_point(field_release:grpc.UserEvent.syncStartedEvent) + if (_internal_has_syncstartedevent()) { + clear_has_event(); + ::grpc::SyncStartedEvent* temp = _impl_.event_.syncstartedevent_; + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } + _impl_.event_.syncstartedevent_ = nullptr; + return temp; + } else { + return nullptr; + } +} +inline const ::grpc::SyncStartedEvent& UserEvent::_internal_syncstartedevent() const { + return _internal_has_syncstartedevent() + ? *_impl_.event_.syncstartedevent_ + : reinterpret_cast< ::grpc::SyncStartedEvent&>(::grpc::_SyncStartedEvent_default_instance_); +} +inline const ::grpc::SyncStartedEvent& UserEvent::syncstartedevent() const { + // @@protoc_insertion_point(field_get:grpc.UserEvent.syncStartedEvent) + return _internal_syncstartedevent(); +} +inline ::grpc::SyncStartedEvent* UserEvent::unsafe_arena_release_syncstartedevent() { + // @@protoc_insertion_point(field_unsafe_arena_release:grpc.UserEvent.syncStartedEvent) + if (_internal_has_syncstartedevent()) { + clear_has_event(); + ::grpc::SyncStartedEvent* temp = _impl_.event_.syncstartedevent_; + _impl_.event_.syncstartedevent_ = nullptr; + return temp; + } else { + return nullptr; + } +} +inline void UserEvent::unsafe_arena_set_allocated_syncstartedevent(::grpc::SyncStartedEvent* syncstartedevent) { + clear_event(); + if (syncstartedevent) { + set_has_syncstartedevent(); + _impl_.event_.syncstartedevent_ = syncstartedevent; + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:grpc.UserEvent.syncStartedEvent) +} +inline ::grpc::SyncStartedEvent* UserEvent::_internal_mutable_syncstartedevent() { + if (!_internal_has_syncstartedevent()) { + clear_event(); + set_has_syncstartedevent(); + _impl_.event_.syncstartedevent_ = CreateMaybeMessage< ::grpc::SyncStartedEvent >(GetArenaForAllocation()); + } + return _impl_.event_.syncstartedevent_; +} +inline ::grpc::SyncStartedEvent* UserEvent::mutable_syncstartedevent() { + ::grpc::SyncStartedEvent* _msg = _internal_mutable_syncstartedevent(); + // @@protoc_insertion_point(field_mutable:grpc.UserEvent.syncStartedEvent) + return _msg; +} + +// .grpc.SyncFinishedEvent syncFinishedEvent = 8; +inline bool UserEvent::_internal_has_syncfinishedevent() const { + return event_case() == kSyncFinishedEvent; +} +inline bool UserEvent::has_syncfinishedevent() const { + return _internal_has_syncfinishedevent(); +} +inline void UserEvent::set_has_syncfinishedevent() { + _impl_._oneof_case_[0] = kSyncFinishedEvent; +} +inline void UserEvent::clear_syncfinishedevent() { + if (_internal_has_syncfinishedevent()) { + if (GetArenaForAllocation() == nullptr) { + delete _impl_.event_.syncfinishedevent_; + } + clear_has_event(); + } +} +inline ::grpc::SyncFinishedEvent* UserEvent::release_syncfinishedevent() { + // @@protoc_insertion_point(field_release:grpc.UserEvent.syncFinishedEvent) + if (_internal_has_syncfinishedevent()) { + clear_has_event(); + ::grpc::SyncFinishedEvent* temp = _impl_.event_.syncfinishedevent_; + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } + _impl_.event_.syncfinishedevent_ = nullptr; + return temp; + } else { + return nullptr; + } +} +inline const ::grpc::SyncFinishedEvent& UserEvent::_internal_syncfinishedevent() const { + return _internal_has_syncfinishedevent() + ? *_impl_.event_.syncfinishedevent_ + : reinterpret_cast< ::grpc::SyncFinishedEvent&>(::grpc::_SyncFinishedEvent_default_instance_); +} +inline const ::grpc::SyncFinishedEvent& UserEvent::syncfinishedevent() const { + // @@protoc_insertion_point(field_get:grpc.UserEvent.syncFinishedEvent) + return _internal_syncfinishedevent(); +} +inline ::grpc::SyncFinishedEvent* UserEvent::unsafe_arena_release_syncfinishedevent() { + // @@protoc_insertion_point(field_unsafe_arena_release:grpc.UserEvent.syncFinishedEvent) + if (_internal_has_syncfinishedevent()) { + clear_has_event(); + ::grpc::SyncFinishedEvent* temp = _impl_.event_.syncfinishedevent_; + _impl_.event_.syncfinishedevent_ = nullptr; + return temp; + } else { + return nullptr; + } +} +inline void UserEvent::unsafe_arena_set_allocated_syncfinishedevent(::grpc::SyncFinishedEvent* syncfinishedevent) { + clear_event(); + if (syncfinishedevent) { + set_has_syncfinishedevent(); + _impl_.event_.syncfinishedevent_ = syncfinishedevent; + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:grpc.UserEvent.syncFinishedEvent) +} +inline ::grpc::SyncFinishedEvent* UserEvent::_internal_mutable_syncfinishedevent() { + if (!_internal_has_syncfinishedevent()) { + clear_event(); + set_has_syncfinishedevent(); + _impl_.event_.syncfinishedevent_ = CreateMaybeMessage< ::grpc::SyncFinishedEvent >(GetArenaForAllocation()); + } + return _impl_.event_.syncfinishedevent_; +} +inline ::grpc::SyncFinishedEvent* UserEvent::mutable_syncfinishedevent() { + ::grpc::SyncFinishedEvent* _msg = _internal_mutable_syncfinishedevent(); + // @@protoc_insertion_point(field_mutable:grpc.UserEvent.syncFinishedEvent) + return _msg; +} + +// .grpc.SyncProgressEvent syncProgressEvent = 9; +inline bool UserEvent::_internal_has_syncprogressevent() const { + return event_case() == kSyncProgressEvent; +} +inline bool UserEvent::has_syncprogressevent() const { + return _internal_has_syncprogressevent(); +} +inline void UserEvent::set_has_syncprogressevent() { + _impl_._oneof_case_[0] = kSyncProgressEvent; +} +inline void UserEvent::clear_syncprogressevent() { + if (_internal_has_syncprogressevent()) { + if (GetArenaForAllocation() == nullptr) { + delete _impl_.event_.syncprogressevent_; + } + clear_has_event(); + } +} +inline ::grpc::SyncProgressEvent* UserEvent::release_syncprogressevent() { + // @@protoc_insertion_point(field_release:grpc.UserEvent.syncProgressEvent) + if (_internal_has_syncprogressevent()) { + clear_has_event(); + ::grpc::SyncProgressEvent* temp = _impl_.event_.syncprogressevent_; + if (GetArenaForAllocation() != nullptr) { + temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp); + } + _impl_.event_.syncprogressevent_ = nullptr; + return temp; + } else { + return nullptr; + } +} +inline const ::grpc::SyncProgressEvent& UserEvent::_internal_syncprogressevent() const { + return _internal_has_syncprogressevent() + ? *_impl_.event_.syncprogressevent_ + : reinterpret_cast< ::grpc::SyncProgressEvent&>(::grpc::_SyncProgressEvent_default_instance_); +} +inline const ::grpc::SyncProgressEvent& UserEvent::syncprogressevent() const { + // @@protoc_insertion_point(field_get:grpc.UserEvent.syncProgressEvent) + return _internal_syncprogressevent(); +} +inline ::grpc::SyncProgressEvent* UserEvent::unsafe_arena_release_syncprogressevent() { + // @@protoc_insertion_point(field_unsafe_arena_release:grpc.UserEvent.syncProgressEvent) + if (_internal_has_syncprogressevent()) { + clear_has_event(); + ::grpc::SyncProgressEvent* temp = _impl_.event_.syncprogressevent_; + _impl_.event_.syncprogressevent_ = nullptr; + return temp; + } else { + return nullptr; + } +} +inline void UserEvent::unsafe_arena_set_allocated_syncprogressevent(::grpc::SyncProgressEvent* syncprogressevent) { + clear_event(); + if (syncprogressevent) { + set_has_syncprogressevent(); + _impl_.event_.syncprogressevent_ = syncprogressevent; + } + // @@protoc_insertion_point(field_unsafe_arena_set_allocated:grpc.UserEvent.syncProgressEvent) +} +inline ::grpc::SyncProgressEvent* UserEvent::_internal_mutable_syncprogressevent() { + if (!_internal_has_syncprogressevent()) { + clear_event(); + set_has_syncprogressevent(); + _impl_.event_.syncprogressevent_ = CreateMaybeMessage< ::grpc::SyncProgressEvent >(GetArenaForAllocation()); + } + return _impl_.event_.syncprogressevent_; +} +inline ::grpc::SyncProgressEvent* UserEvent::mutable_syncprogressevent() { + ::grpc::SyncProgressEvent* _msg = _internal_mutable_syncprogressevent(); + // @@protoc_insertion_point(field_mutable:grpc.UserEvent.syncProgressEvent) + return _msg; +} + inline bool UserEvent::has_event() const { return event_case() != EVENT_NOT_SET; } @@ -16247,6 +17556,356 @@ inline void UserBadEvent::set_allocated_errormessage(std::string* errormessage) // ------------------------------------------------------------------- +// UsedBytesChangedEvent + +// string userID = 1; +inline void UsedBytesChangedEvent::clear_userid() { + _impl_.userid_.ClearToEmpty(); +} +inline const std::string& UsedBytesChangedEvent::userid() const { + // @@protoc_insertion_point(field_get:grpc.UsedBytesChangedEvent.userID) + return _internal_userid(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void UsedBytesChangedEvent::set_userid(ArgT0&& arg0, ArgT... args) { + + _impl_.userid_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:grpc.UsedBytesChangedEvent.userID) +} +inline std::string* UsedBytesChangedEvent::mutable_userid() { + std::string* _s = _internal_mutable_userid(); + // @@protoc_insertion_point(field_mutable:grpc.UsedBytesChangedEvent.userID) + return _s; +} +inline const std::string& UsedBytesChangedEvent::_internal_userid() const { + return _impl_.userid_.Get(); +} +inline void UsedBytesChangedEvent::_internal_set_userid(const std::string& value) { + + _impl_.userid_.Set(value, GetArenaForAllocation()); +} +inline std::string* UsedBytesChangedEvent::_internal_mutable_userid() { + + return _impl_.userid_.Mutable(GetArenaForAllocation()); +} +inline std::string* UsedBytesChangedEvent::release_userid() { + // @@protoc_insertion_point(field_release:grpc.UsedBytesChangedEvent.userID) + return _impl_.userid_.Release(); +} +inline void UsedBytesChangedEvent::set_allocated_userid(std::string* userid) { + if (userid != nullptr) { + + } else { + + } + _impl_.userid_.SetAllocated(userid, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.userid_.IsDefault()) { + _impl_.userid_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:grpc.UsedBytesChangedEvent.userID) +} + +// int64 usedBytes = 2; +inline void UsedBytesChangedEvent::clear_usedbytes() { + _impl_.usedbytes_ = int64_t{0}; +} +inline int64_t UsedBytesChangedEvent::_internal_usedbytes() const { + return _impl_.usedbytes_; +} +inline int64_t UsedBytesChangedEvent::usedbytes() const { + // @@protoc_insertion_point(field_get:grpc.UsedBytesChangedEvent.usedBytes) + return _internal_usedbytes(); +} +inline void UsedBytesChangedEvent::_internal_set_usedbytes(int64_t value) { + + _impl_.usedbytes_ = value; +} +inline void UsedBytesChangedEvent::set_usedbytes(int64_t value) { + _internal_set_usedbytes(value); + // @@protoc_insertion_point(field_set:grpc.UsedBytesChangedEvent.usedBytes) +} + +// ------------------------------------------------------------------- + +// ImapLoginFailedEvent + +// string username = 1; +inline void ImapLoginFailedEvent::clear_username() { + _impl_.username_.ClearToEmpty(); +} +inline const std::string& ImapLoginFailedEvent::username() const { + // @@protoc_insertion_point(field_get:grpc.ImapLoginFailedEvent.username) + return _internal_username(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void ImapLoginFailedEvent::set_username(ArgT0&& arg0, ArgT... args) { + + _impl_.username_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:grpc.ImapLoginFailedEvent.username) +} +inline std::string* ImapLoginFailedEvent::mutable_username() { + std::string* _s = _internal_mutable_username(); + // @@protoc_insertion_point(field_mutable:grpc.ImapLoginFailedEvent.username) + return _s; +} +inline const std::string& ImapLoginFailedEvent::_internal_username() const { + return _impl_.username_.Get(); +} +inline void ImapLoginFailedEvent::_internal_set_username(const std::string& value) { + + _impl_.username_.Set(value, GetArenaForAllocation()); +} +inline std::string* ImapLoginFailedEvent::_internal_mutable_username() { + + return _impl_.username_.Mutable(GetArenaForAllocation()); +} +inline std::string* ImapLoginFailedEvent::release_username() { + // @@protoc_insertion_point(field_release:grpc.ImapLoginFailedEvent.username) + return _impl_.username_.Release(); +} +inline void ImapLoginFailedEvent::set_allocated_username(std::string* username) { + if (username != nullptr) { + + } else { + + } + _impl_.username_.SetAllocated(username, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.username_.IsDefault()) { + _impl_.username_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:grpc.ImapLoginFailedEvent.username) +} + +// ------------------------------------------------------------------- + +// SyncStartedEvent + +// string userID = 1; +inline void SyncStartedEvent::clear_userid() { + _impl_.userid_.ClearToEmpty(); +} +inline const std::string& SyncStartedEvent::userid() const { + // @@protoc_insertion_point(field_get:grpc.SyncStartedEvent.userID) + return _internal_userid(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void SyncStartedEvent::set_userid(ArgT0&& arg0, ArgT... args) { + + _impl_.userid_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:grpc.SyncStartedEvent.userID) +} +inline std::string* SyncStartedEvent::mutable_userid() { + std::string* _s = _internal_mutable_userid(); + // @@protoc_insertion_point(field_mutable:grpc.SyncStartedEvent.userID) + return _s; +} +inline const std::string& SyncStartedEvent::_internal_userid() const { + return _impl_.userid_.Get(); +} +inline void SyncStartedEvent::_internal_set_userid(const std::string& value) { + + _impl_.userid_.Set(value, GetArenaForAllocation()); +} +inline std::string* SyncStartedEvent::_internal_mutable_userid() { + + return _impl_.userid_.Mutable(GetArenaForAllocation()); +} +inline std::string* SyncStartedEvent::release_userid() { + // @@protoc_insertion_point(field_release:grpc.SyncStartedEvent.userID) + return _impl_.userid_.Release(); +} +inline void SyncStartedEvent::set_allocated_userid(std::string* userid) { + if (userid != nullptr) { + + } else { + + } + _impl_.userid_.SetAllocated(userid, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.userid_.IsDefault()) { + _impl_.userid_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:grpc.SyncStartedEvent.userID) +} + +// ------------------------------------------------------------------- + +// SyncFinishedEvent + +// string userID = 1; +inline void SyncFinishedEvent::clear_userid() { + _impl_.userid_.ClearToEmpty(); +} +inline const std::string& SyncFinishedEvent::userid() const { + // @@protoc_insertion_point(field_get:grpc.SyncFinishedEvent.userID) + return _internal_userid(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void SyncFinishedEvent::set_userid(ArgT0&& arg0, ArgT... args) { + + _impl_.userid_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:grpc.SyncFinishedEvent.userID) +} +inline std::string* SyncFinishedEvent::mutable_userid() { + std::string* _s = _internal_mutable_userid(); + // @@protoc_insertion_point(field_mutable:grpc.SyncFinishedEvent.userID) + return _s; +} +inline const std::string& SyncFinishedEvent::_internal_userid() const { + return _impl_.userid_.Get(); +} +inline void SyncFinishedEvent::_internal_set_userid(const std::string& value) { + + _impl_.userid_.Set(value, GetArenaForAllocation()); +} +inline std::string* SyncFinishedEvent::_internal_mutable_userid() { + + return _impl_.userid_.Mutable(GetArenaForAllocation()); +} +inline std::string* SyncFinishedEvent::release_userid() { + // @@protoc_insertion_point(field_release:grpc.SyncFinishedEvent.userID) + return _impl_.userid_.Release(); +} +inline void SyncFinishedEvent::set_allocated_userid(std::string* userid) { + if (userid != nullptr) { + + } else { + + } + _impl_.userid_.SetAllocated(userid, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.userid_.IsDefault()) { + _impl_.userid_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:grpc.SyncFinishedEvent.userID) +} + +// ------------------------------------------------------------------- + +// SyncProgressEvent + +// string userID = 1; +inline void SyncProgressEvent::clear_userid() { + _impl_.userid_.ClearToEmpty(); +} +inline const std::string& SyncProgressEvent::userid() const { + // @@protoc_insertion_point(field_get:grpc.SyncProgressEvent.userID) + return _internal_userid(); +} +template +inline PROTOBUF_ALWAYS_INLINE +void SyncProgressEvent::set_userid(ArgT0&& arg0, ArgT... args) { + + _impl_.userid_.Set(static_cast(arg0), args..., GetArenaForAllocation()); + // @@protoc_insertion_point(field_set:grpc.SyncProgressEvent.userID) +} +inline std::string* SyncProgressEvent::mutable_userid() { + std::string* _s = _internal_mutable_userid(); + // @@protoc_insertion_point(field_mutable:grpc.SyncProgressEvent.userID) + return _s; +} +inline const std::string& SyncProgressEvent::_internal_userid() const { + return _impl_.userid_.Get(); +} +inline void SyncProgressEvent::_internal_set_userid(const std::string& value) { + + _impl_.userid_.Set(value, GetArenaForAllocation()); +} +inline std::string* SyncProgressEvent::_internal_mutable_userid() { + + return _impl_.userid_.Mutable(GetArenaForAllocation()); +} +inline std::string* SyncProgressEvent::release_userid() { + // @@protoc_insertion_point(field_release:grpc.SyncProgressEvent.userID) + return _impl_.userid_.Release(); +} +inline void SyncProgressEvent::set_allocated_userid(std::string* userid) { + if (userid != nullptr) { + + } else { + + } + _impl_.userid_.SetAllocated(userid, GetArenaForAllocation()); +#ifdef PROTOBUF_FORCE_COPY_DEFAULT_STRING + if (_impl_.userid_.IsDefault()) { + _impl_.userid_.Set("", GetArenaForAllocation()); + } +#endif // PROTOBUF_FORCE_COPY_DEFAULT_STRING + // @@protoc_insertion_point(field_set_allocated:grpc.SyncProgressEvent.userID) +} + +// double progress = 2; +inline void SyncProgressEvent::clear_progress() { + _impl_.progress_ = 0; +} +inline double SyncProgressEvent::_internal_progress() const { + return _impl_.progress_; +} +inline double SyncProgressEvent::progress() const { + // @@protoc_insertion_point(field_get:grpc.SyncProgressEvent.progress) + return _internal_progress(); +} +inline void SyncProgressEvent::_internal_set_progress(double value) { + + _impl_.progress_ = value; +} +inline void SyncProgressEvent::set_progress(double value) { + _internal_set_progress(value); + // @@protoc_insertion_point(field_set:grpc.SyncProgressEvent.progress) +} + +// int64 elapsedMs = 3; +inline void SyncProgressEvent::clear_elapsedms() { + _impl_.elapsedms_ = int64_t{0}; +} +inline int64_t SyncProgressEvent::_internal_elapsedms() const { + return _impl_.elapsedms_; +} +inline int64_t SyncProgressEvent::elapsedms() const { + // @@protoc_insertion_point(field_get:grpc.SyncProgressEvent.elapsedMs) + return _internal_elapsedms(); +} +inline void SyncProgressEvent::_internal_set_elapsedms(int64_t value) { + + _impl_.elapsedms_ = value; +} +inline void SyncProgressEvent::set_elapsedms(int64_t value) { + _internal_set_elapsedms(value); + // @@protoc_insertion_point(field_set:grpc.SyncProgressEvent.elapsedMs) +} + +// int64 remainingMs = 4; +inline void SyncProgressEvent::clear_remainingms() { + _impl_.remainingms_ = int64_t{0}; +} +inline int64_t SyncProgressEvent::_internal_remainingms() const { + return _impl_.remainingms_; +} +inline int64_t SyncProgressEvent::remainingms() const { + // @@protoc_insertion_point(field_get:grpc.SyncProgressEvent.remainingMs) + return _internal_remainingms(); +} +inline void SyncProgressEvent::_internal_set_remainingms(int64_t value) { + + _impl_.remainingms_ = value; +} +inline void SyncProgressEvent::set_remainingms(int64_t value) { + _internal_set_remainingms(value); + // @@protoc_insertion_point(field_set:grpc.SyncProgressEvent.remainingMs) +} + +// ------------------------------------------------------------------- + // GenericErrorEvent // .grpc.ErrorCode code = 1; @@ -16388,6 +18047,16 @@ inline void GenericErrorEvent::set_code(::grpc::ErrorCode value) { // ------------------------------------------------------------------- +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + +// ------------------------------------------------------------------- + // @@protoc_insertion_point(namespace_scope) diff --git a/internal/frontend/bridge-gui/bridgepp/bridgepp/User/User.cpp b/internal/frontend/bridge-gui/bridgepp/bridgepp/User/User.cpp index 382a5d50..041a4d12 100644 --- a/internal/frontend/bridge-gui/bridgepp/bridgepp/User/User.cpp +++ b/internal/frontend/bridge-gui/bridgepp/bridgepp/User/User.cpp @@ -34,7 +34,8 @@ SPUser User::newUser(QObject *parent) { /// \param[in] parent The parent object. //**************************************************************************************************************************************************** User::User(QObject *parent) - : QObject(parent) { + : QObject(parent) + , imapFailureCooldownEndTime_(QDateTime::currentDateTime()) { } @@ -293,6 +294,48 @@ void User::setTotalBytes(float totalBytes) { } +//**************************************************************************************************************************************************** +/// \return true iff a sync is in progress. +//**************************************************************************************************************************************************** +bool User::isSyncing() const { + return isSyncing_; +} + + +//**************************************************************************************************************************************************** +/// \param[in] syncing The new value for the sync state. +//**************************************************************************************************************************************************** +void User::setIsSyncing(bool syncing) { + if (isSyncing_ == syncing) { + return; + } + + isSyncing_ = syncing; + emit isSyncingChanged(syncing); +} + + +//**************************************************************************************************************************************************** +/// \return The sync progress ratio +//**************************************************************************************************************************************************** +float User::syncProgress() const { + return syncProgress_; +} + + +//**************************************************************************************************************************************************** +/// \param[in] progress The progress ratio. +//**************************************************************************************************************************************************** +void User::setSyncProgress(float progress) { + if (qAbs(syncProgress_ - progress) < 0.00001) { + return; + } + + syncProgress_ = progress; + emit syncProgressChanged(progress); +} + + //**************************************************************************************************************************************************** /// \param[in] state The user state. /// \return A string describing the state. @@ -311,4 +354,24 @@ QString User::stateToString(UserState state) { } +//**************************************************************************************************************************************************** +/// We display a notification and pop the application window if an IMAP client tries to connect to a signed out account, but we do not want to +/// do it repeatedly, as it's an intrusive action. This function let's you define a period of time during which the notification should not be +/// displayed. +/// +/// \param durationMSecs The duration of the period in milliseconds. +//**************************************************************************************************************************************************** +void User::startImapLoginFailureCooldown(qint64 durationMSecs) { + imapFailureCooldownEndTime_ = QDateTime::currentDateTime().addMSecs(durationMSecs); +} + + +//**************************************************************************************************************************************************** +/// \return true if we currently are in a cooldown period for the notification +//**************************************************************************************************************************************************** +bool User::isInIMAPLoginFailureCooldown() const { + return QDateTime::currentDateTime() < imapFailureCooldownEndTime_; +} + + } // namespace bridgepp diff --git a/internal/frontend/bridge-gui/bridgepp/bridgepp/User/User.h b/internal/frontend/bridge-gui/bridgepp/bridgepp/User/User.h index 14a82249..78cfe8c9 100644 --- a/internal/frontend/bridge-gui/bridgepp/bridgepp/User/User.h +++ b/internal/frontend/bridge-gui/bridgepp/bridgepp/User/User.h @@ -74,6 +74,8 @@ public: // member functions. User &operator=(User &&) = delete; ///< Disabled move assignment operator. void update(User const &user); ///< Update the user. Q_INVOKABLE QString primaryEmailOrUsername() const; ///< Return the user primary email, or, if unknown its username. + void startImapLoginFailureCooldown(qint64 durationMSecs); ///< Start the user cooldown period for the IMAP login attempt while signed-out notification. + bool isInIMAPLoginFailureCooldown() const; ///< Check if the user in a IMAP login failure notification. public slots: // slots for QML generated calls @@ -99,6 +101,8 @@ public: Q_PROPERTY(bool splitMode READ splitMode WRITE setSplitMode NOTIFY splitModeChanged) Q_PROPERTY(float usedBytes READ usedBytes WRITE setUsedBytes NOTIFY usedBytesChanged) Q_PROPERTY(float totalBytes READ totalBytes WRITE setTotalBytes NOTIFY totalBytesChanged) + Q_PROPERTY(bool isSyncing READ isSyncing WRITE setIsSyncing NOTIFY isSyncingChanged) + Q_PROPERTY(float syncProgress READ syncProgress WRITE setSyncProgress NOTIFY syncProgressChanged) QString id() const; void setID(QString const &id); @@ -118,6 +122,10 @@ public: void setUsedBytes(float usedBytes); float totalBytes() const; void setTotalBytes(float totalBytes); + bool isSyncing() const; + void setIsSyncing(bool syncing); + float syncProgress() const; + void setSyncProgress(float progress); signals: // signals used for Qt properties @@ -132,11 +140,14 @@ signals: void usedBytesChanged(float byteCount); void totalBytesChanged(float byteCount); void toggleSplitModeFinished(); + void isSyncingChanged(bool syncing); + void syncProgressChanged(float syncProgress); private: // member functions. User(QObject *parent); ///< Default constructor. private: // data members. + QDateTime imapFailureCooldownEndTime_; ///< The end date/time for the IMAP login failure notification cooldown period. QString id_; ///< The userID. QString username_; ///< The username QString password_; ///< The IMAP password of the user. @@ -146,6 +157,8 @@ private: // data members. bool splitMode_ { false }; ///< Is split mode active. float usedBytes_ { 0.0f }; ///< The storage used by the user. float totalBytes_ { 1.0f }; ///< The storage quota of the user. + bool isSyncing_ { false }; ///< Is a sync in progress for the user. + float syncProgress_ { 0.0f }; ///< The sync progress. }; diff --git a/internal/frontend/bridge-gui/bridgepp/bridgepp/Worker/Overseer.cpp b/internal/frontend/bridge-gui/bridgepp/bridgepp/Worker/Overseer.cpp index ed4553bc..8a5da3b4 100644 --- a/internal/frontend/bridge-gui/bridgepp/bridgepp/Worker/Overseer.cpp +++ b/internal/frontend/bridge-gui/bridgepp/bridgepp/Worker/Overseer.cpp @@ -84,7 +84,8 @@ void Overseer::releaseWorker() { if (thread_) { if (!thread_->isFinished()) { thread_->quit(); - thread_->wait(); + if (!thread_->wait(maxTerminationWaitTimeMs)) + thread_->terminate(); } thread_->deleteLater(); thread_ = nullptr; diff --git a/internal/frontend/bridge-gui/bridgepp/bridgepp/Worker/Overseer.h b/internal/frontend/bridge-gui/bridgepp/bridgepp/Worker/Overseer.h index 26943899..e4bdd47a 100644 --- a/internal/frontend/bridge-gui/bridgepp/bridgepp/Worker/Overseer.h +++ b/internal/frontend/bridge-gui/bridgepp/bridgepp/Worker/Overseer.h @@ -46,6 +46,9 @@ public slots: void startWorker(bool autorelease) const; ///< Run the worker. void releaseWorker(); ///< Delete the worker and its thread. +public: // static data members + static qint64 const maxTerminationWaitTimeMs { 10000 }; ///< The maximum wait time for the termination of a thread + public: // data members. QThread *thread_ { nullptr }; ///< The thread. Worker *worker_ { nullptr }; ///< The worker. diff --git a/internal/frontend/cli/accounts.go b/internal/frontend/cli/accounts.go index 890d4ccc..09e3c15a 100644 --- a/internal/frontend/cli/accounts.go +++ b/internal/frontend/cli/accounts.go @@ -115,7 +115,7 @@ func (f *frontendCLI) showAccountAddressInfo(user bridge.UserInfo, address strin f.Println("") } -func (f *frontendCLI) loginAccount(c *ishell.Context) { //nolint:funlen +func (f *frontendCLI) loginAccount(c *ishell.Context) { f.ShowPrompt(false) defer f.ShowPrompt(true) diff --git a/internal/frontend/cli/frontend.go b/internal/frontend/cli/frontend.go index 180cdab1..4063f416 100644 --- a/internal/frontend/cli/frontend.go +++ b/internal/frontend/cli/frontend.go @@ -42,7 +42,7 @@ type frontendCLI struct { } // New returns a new CLI frontend configured with the given options. -func New(bridge *bridge.Bridge, restarter *restarter.Restarter, eventCh <-chan events.Event) *frontendCLI { //nolint:funlen,revive +func New(bridge *bridge.Bridge, restarter *restarter.Restarter, eventCh <-chan events.Event) *frontendCLI { //nolint:revive fe := &frontendCLI{ Shell: ishell.New(), bridge: bridge, @@ -138,12 +138,16 @@ func New(bridge *bridge.Bridge, restarter *restarter.Restarter, eventCh <-chan e fe.AddCmd(configureCmd) // TLS commands. - exportTLSCmd := &ishell.Cmd{ - Name: "export-tls", + fe.AddCmd(&ishell.Cmd{ + Name: "export-tls-cert", Help: "Export the TLS certificate used by the Bridge", Func: fe.exportTLSCerts, - } - fe.AddCmd(exportTLSCmd) + }) + fe.AddCmd(&ishell.Cmd{ + Name: "import-tls-cert", + Help: "Import a TLS certificate to be used by the Bridge", + Func: fe.importTLSCerts, + }) // All mail visibility commands. allMailCmd := &ishell.Cmd{ @@ -280,7 +284,7 @@ func New(bridge *bridge.Bridge, restarter *restarter.Restarter, eventCh <-chan e return fe } -func (f *frontendCLI) watchEvents(eventCh <-chan events.Event) { // nolint:funlen +func (f *frontendCLI) watchEvents(eventCh <-chan events.Event) { // nolint:gocyclo // GODT-1949: Better error events. for _, err := range f.bridge.GetErrors() { switch { @@ -289,12 +293,6 @@ func (f *frontendCLI) watchEvents(eventCh <-chan events.Event) { // nolint:funle case errors.Is(err, bridge.ErrVaultInsecure): f.notifyCredentialsError() - - case errors.Is(err, bridge.ErrServeIMAP): - f.Println("IMAP server error:", err) - - case errors.Is(err, bridge.ErrServeSMTP): - f.Println("SMTP server error:", err) } } @@ -306,6 +304,12 @@ func (f *frontendCLI) watchEvents(eventCh <-chan events.Event) { // nolint:funle case events.ConnStatusDown: f.notifyInternetOff() + case events.IMAPServerError: + f.Println("IMAP server error:", event.Error) + + case events.SMTPServerError: + f.Println("SMTP server error:", event.Error) + case events.UserDeauth: user, err := f.bridge.GetUserInfo(event.UserID) if err != nil { @@ -331,6 +335,17 @@ func (f *frontendCLI) watchEvents(eventCh <-chan events.Event) { // nolint:funle f.Printf("* bad-event synchronize\n") f.Printf("* bad-event logout\n\n") + case events.IMAPLoginFailed: + f.Printf("An IMAP login attempt failed for user %v\n", event.Username) + + case events.UserAddressEnabled: + user, err := f.bridge.GetUserInfo(event.UserID) + if err != nil { + return + } + + f.Printf("An address for %s was enabled. You may need to reconfigure your email client.\n", user.Username) + case events.UserAddressUpdated: user, err := f.bridge.GetUserInfo(event.UserID) if err != nil { @@ -339,8 +354,21 @@ func (f *frontendCLI) watchEvents(eventCh <-chan events.Event) { // nolint:funle f.Printf("Address changed for %s. You may need to reconfigure your email client.\n", user.Username) + case events.UserAddressDisabled: + user, err := f.bridge.GetUserInfo(event.UserID) + if err != nil { + return + } + + f.Printf("An address for %s was disabled. You may need to reconfigure your email client.\n", user.Username) + case events.UserAddressDeleted: - f.notifyLogout(event.Email) + user, err := f.bridge.GetUserInfo(event.UserID) + if err != nil { + return + } + + f.Printf("An address for %s was disabled. You may need to reconfigure your email client.\n", user.Username) case events.SyncStarted: user, err := f.bridge.GetUserInfo(event.UserID) diff --git a/internal/frontend/cli/system.go b/internal/frontend/cli/system.go index c92df83f..dc1b12ca 100644 --- a/internal/frontend/cli/system.go +++ b/internal/frontend/cli/system.go @@ -226,6 +226,27 @@ func (f *frontendCLI) exportTLSCerts(c *ishell.Context) { } } +func (f *frontendCLI) importTLSCerts(c *ishell.Context) { + certPath := f.readStringInAttempts("Enter the path to the cert.pem file", c.ReadLine, f.isFile) + if certPath == "" { + f.printAndLogError(errors.New("failed to get cert path")) + return + } + + keyPath := f.readStringInAttempts("Enter the path to the key.pem file", c.ReadLine, f.isFile) + if keyPath == "" { + f.printAndLogError(errors.New("failed to get key path")) + return + } + + if err := f.bridge.SetBridgeTLSCertPath(certPath, keyPath); err != nil { + f.printAndLogError(err) + return + } + + f.Println("TLS certificate imported. Restart Bridge to use it.") +} + func (f *frontendCLI) isPortFree(port string) bool { port = strings.ReplaceAll(port, ":", "") if port == "" { @@ -252,3 +273,12 @@ func (f *frontendCLI) isCacheLocationUsable(location string) bool { return stat.IsDir() } + +func (f *frontendCLI) isFile(location string) bool { + stat, err := os.Stat(location) + if err != nil { + return false + } + + return !stat.IsDir() +} diff --git a/internal/frontend/grpc/bridge.pb.go b/internal/frontend/grpc/bridge.pb.go index 6d8b05fd..5a4680e2 100644 --- a/internal/frontend/grpc/bridge.pb.go +++ b/internal/frontend/grpc/bridge.pb.go @@ -3617,6 +3617,11 @@ type UserEvent struct { // *UserEvent_UserDisconnected // *UserEvent_UserChanged // *UserEvent_UserBadEvent + // *UserEvent_UsedBytesChangedEvent + // *UserEvent_ImapLoginFailedEvent + // *UserEvent_SyncStartedEvent + // *UserEvent_SyncFinishedEvent + // *UserEvent_SyncProgressEvent Event isUserEvent_Event `protobuf_oneof:"event"` } @@ -3687,6 +3692,41 @@ func (x *UserEvent) GetUserBadEvent() *UserBadEvent { return nil } +func (x *UserEvent) GetUsedBytesChangedEvent() *UsedBytesChangedEvent { + if x, ok := x.GetEvent().(*UserEvent_UsedBytesChangedEvent); ok { + return x.UsedBytesChangedEvent + } + return nil +} + +func (x *UserEvent) GetImapLoginFailedEvent() *ImapLoginFailedEvent { + if x, ok := x.GetEvent().(*UserEvent_ImapLoginFailedEvent); ok { + return x.ImapLoginFailedEvent + } + return nil +} + +func (x *UserEvent) GetSyncStartedEvent() *SyncStartedEvent { + if x, ok := x.GetEvent().(*UserEvent_SyncStartedEvent); ok { + return x.SyncStartedEvent + } + return nil +} + +func (x *UserEvent) GetSyncFinishedEvent() *SyncFinishedEvent { + if x, ok := x.GetEvent().(*UserEvent_SyncFinishedEvent); ok { + return x.SyncFinishedEvent + } + return nil +} + +func (x *UserEvent) GetSyncProgressEvent() *SyncProgressEvent { + if x, ok := x.GetEvent().(*UserEvent_SyncProgressEvent); ok { + return x.SyncProgressEvent + } + return nil +} + type isUserEvent_Event interface { isUserEvent_Event() } @@ -3707,6 +3747,26 @@ type UserEvent_UserBadEvent struct { UserBadEvent *UserBadEvent `protobuf:"bytes,4,opt,name=userBadEvent,proto3,oneof"` } +type UserEvent_UsedBytesChangedEvent struct { + UsedBytesChangedEvent *UsedBytesChangedEvent `protobuf:"bytes,5,opt,name=usedBytesChangedEvent,proto3,oneof"` +} + +type UserEvent_ImapLoginFailedEvent struct { + ImapLoginFailedEvent *ImapLoginFailedEvent `protobuf:"bytes,6,opt,name=imapLoginFailedEvent,proto3,oneof"` +} + +type UserEvent_SyncStartedEvent struct { + SyncStartedEvent *SyncStartedEvent `protobuf:"bytes,7,opt,name=syncStartedEvent,proto3,oneof"` +} + +type UserEvent_SyncFinishedEvent struct { + SyncFinishedEvent *SyncFinishedEvent `protobuf:"bytes,8,opt,name=syncFinishedEvent,proto3,oneof"` +} + +type UserEvent_SyncProgressEvent struct { + SyncProgressEvent *SyncProgressEvent `protobuf:"bytes,9,opt,name=syncProgressEvent,proto3,oneof"` +} + func (*UserEvent_ToggleSplitModeFinished) isUserEvent_Event() {} func (*UserEvent_UserDisconnected) isUserEvent_Event() {} @@ -3715,6 +3775,16 @@ func (*UserEvent_UserChanged) isUserEvent_Event() {} func (*UserEvent_UserBadEvent) isUserEvent_Event() {} +func (*UserEvent_UsedBytesChangedEvent) isUserEvent_Event() {} + +func (*UserEvent_ImapLoginFailedEvent) isUserEvent_Event() {} + +func (*UserEvent_SyncStartedEvent) isUserEvent_Event() {} + +func (*UserEvent_SyncFinishedEvent) isUserEvent_Event() {} + +func (*UserEvent_SyncProgressEvent) isUserEvent_Event() {} + type ToggleSplitModeFinishedEvent struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -3911,6 +3981,273 @@ func (x *UserBadEvent) GetErrorMessage() string { return "" } +type UsedBytesChangedEvent struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + UserID string `protobuf:"bytes,1,opt,name=userID,proto3" json:"userID,omitempty"` + UsedBytes int64 `protobuf:"varint,2,opt,name=usedBytes,proto3" json:"usedBytes,omitempty"` +} + +func (x *UsedBytesChangedEvent) Reset() { + *x = UsedBytesChangedEvent{} + if protoimpl.UnsafeEnabled { + mi := &file_bridge_proto_msgTypes[58] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *UsedBytesChangedEvent) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UsedBytesChangedEvent) ProtoMessage() {} + +func (x *UsedBytesChangedEvent) ProtoReflect() protoreflect.Message { + mi := &file_bridge_proto_msgTypes[58] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UsedBytesChangedEvent.ProtoReflect.Descriptor instead. +func (*UsedBytesChangedEvent) Descriptor() ([]byte, []int) { + return file_bridge_proto_rawDescGZIP(), []int{58} +} + +func (x *UsedBytesChangedEvent) GetUserID() string { + if x != nil { + return x.UserID + } + return "" +} + +func (x *UsedBytesChangedEvent) GetUsedBytes() int64 { + if x != nil { + return x.UsedBytes + } + return 0 +} + +type ImapLoginFailedEvent struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Username string `protobuf:"bytes,1,opt,name=username,proto3" json:"username,omitempty"` +} + +func (x *ImapLoginFailedEvent) Reset() { + *x = ImapLoginFailedEvent{} + if protoimpl.UnsafeEnabled { + mi := &file_bridge_proto_msgTypes[59] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ImapLoginFailedEvent) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ImapLoginFailedEvent) ProtoMessage() {} + +func (x *ImapLoginFailedEvent) ProtoReflect() protoreflect.Message { + mi := &file_bridge_proto_msgTypes[59] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ImapLoginFailedEvent.ProtoReflect.Descriptor instead. +func (*ImapLoginFailedEvent) Descriptor() ([]byte, []int) { + return file_bridge_proto_rawDescGZIP(), []int{59} +} + +func (x *ImapLoginFailedEvent) GetUsername() string { + if x != nil { + return x.Username + } + return "" +} + +type SyncStartedEvent struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + UserID string `protobuf:"bytes,1,opt,name=userID,proto3" json:"userID,omitempty"` +} + +func (x *SyncStartedEvent) Reset() { + *x = SyncStartedEvent{} + if protoimpl.UnsafeEnabled { + mi := &file_bridge_proto_msgTypes[60] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SyncStartedEvent) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SyncStartedEvent) ProtoMessage() {} + +func (x *SyncStartedEvent) ProtoReflect() protoreflect.Message { + mi := &file_bridge_proto_msgTypes[60] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SyncStartedEvent.ProtoReflect.Descriptor instead. +func (*SyncStartedEvent) Descriptor() ([]byte, []int) { + return file_bridge_proto_rawDescGZIP(), []int{60} +} + +func (x *SyncStartedEvent) GetUserID() string { + if x != nil { + return x.UserID + } + return "" +} + +type SyncFinishedEvent struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + UserID string `protobuf:"bytes,1,opt,name=userID,proto3" json:"userID,omitempty"` +} + +func (x *SyncFinishedEvent) Reset() { + *x = SyncFinishedEvent{} + if protoimpl.UnsafeEnabled { + mi := &file_bridge_proto_msgTypes[61] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SyncFinishedEvent) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SyncFinishedEvent) ProtoMessage() {} + +func (x *SyncFinishedEvent) ProtoReflect() protoreflect.Message { + mi := &file_bridge_proto_msgTypes[61] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SyncFinishedEvent.ProtoReflect.Descriptor instead. +func (*SyncFinishedEvent) Descriptor() ([]byte, []int) { + return file_bridge_proto_rawDescGZIP(), []int{61} +} + +func (x *SyncFinishedEvent) GetUserID() string { + if x != nil { + return x.UserID + } + return "" +} + +type SyncProgressEvent struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + UserID string `protobuf:"bytes,1,opt,name=userID,proto3" json:"userID,omitempty"` + Progress float64 `protobuf:"fixed64,2,opt,name=progress,proto3" json:"progress,omitempty"` + ElapsedMs int64 `protobuf:"varint,3,opt,name=elapsedMs,proto3" json:"elapsedMs,omitempty"` + RemainingMs int64 `protobuf:"varint,4,opt,name=remainingMs,proto3" json:"remainingMs,omitempty"` +} + +func (x *SyncProgressEvent) Reset() { + *x = SyncProgressEvent{} + if protoimpl.UnsafeEnabled { + mi := &file_bridge_proto_msgTypes[62] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *SyncProgressEvent) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SyncProgressEvent) ProtoMessage() {} + +func (x *SyncProgressEvent) ProtoReflect() protoreflect.Message { + mi := &file_bridge_proto_msgTypes[62] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SyncProgressEvent.ProtoReflect.Descriptor instead. +func (*SyncProgressEvent) Descriptor() ([]byte, []int) { + return file_bridge_proto_rawDescGZIP(), []int{62} +} + +func (x *SyncProgressEvent) GetUserID() string { + if x != nil { + return x.UserID + } + return "" +} + +func (x *SyncProgressEvent) GetProgress() float64 { + if x != nil { + return x.Progress + } + return 0 +} + +func (x *SyncProgressEvent) GetElapsedMs() int64 { + if x != nil { + return x.ElapsedMs + } + return 0 +} + +func (x *SyncProgressEvent) GetRemainingMs() int64 { + if x != nil { + return x.RemainingMs + } + return 0 +} + type GenericErrorEvent struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -3922,7 +4259,7 @@ type GenericErrorEvent struct { func (x *GenericErrorEvent) Reset() { *x = GenericErrorEvent{} if protoimpl.UnsafeEnabled { - mi := &file_bridge_proto_msgTypes[58] + mi := &file_bridge_proto_msgTypes[63] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3935,7 +4272,7 @@ func (x *GenericErrorEvent) String() string { func (*GenericErrorEvent) ProtoMessage() {} func (x *GenericErrorEvent) ProtoReflect() protoreflect.Message { - mi := &file_bridge_proto_msgTypes[58] + mi := &file_bridge_proto_msgTypes[63] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3948,7 +4285,7 @@ func (x *GenericErrorEvent) ProtoReflect() protoreflect.Message { // Deprecated: Use GenericErrorEvent.ProtoReflect.Descriptor instead. func (*GenericErrorEvent) Descriptor() ([]byte, []int) { - return file_bridge_proto_rawDescGZIP(), []int{58} + return file_bridge_proto_rawDescGZIP(), []int{63} } func (x *GenericErrorEvent) GetCode() ErrorCode { @@ -4330,7 +4667,7 @@ var file_bridge_proto_rawDesc = []byte{ 0x76, 0x65, 0x6e, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x13, 0x0a, 0x11, 0x41, 0x70, 0x69, 0x43, 0x65, 0x72, 0x74, 0x49, 0x73, 0x73, 0x75, 0x65, 0x45, 0x76, - 0x65, 0x6e, 0x74, 0x22, 0xb5, 0x02, 0x0a, 0x09, 0x55, 0x73, 0x65, 0x72, 0x45, 0x76, 0x65, 0x6e, + 0x65, 0x6e, 0x74, 0x22, 0xb4, 0x05, 0x0a, 0x09, 0x55, 0x73, 0x65, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x5e, 0x0a, 0x17, 0x74, 0x6f, 0x67, 0x67, 0x6c, 0x65, 0x53, 0x70, 0x6c, 0x69, 0x74, 0x4d, 0x6f, 0x64, 0x65, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x54, 0x6f, 0x67, 0x67, 0x6c, 0x65, @@ -4349,321 +4686,368 @@ var file_bridge_proto_rawDesc = []byte{ 0x42, 0x61, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x42, 0x61, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x0c, 0x75, 0x73, 0x65, 0x72, 0x42, 0x61, 0x64, 0x45, 0x76, 0x65, - 0x6e, 0x74, 0x42, 0x07, 0x0a, 0x05, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x36, 0x0a, 0x1c, 0x54, - 0x6f, 0x67, 0x67, 0x6c, 0x65, 0x53, 0x70, 0x6c, 0x69, 0x74, 0x4d, 0x6f, 0x64, 0x65, 0x46, 0x69, - 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x75, + 0x6e, 0x74, 0x12, 0x53, 0x0a, 0x15, 0x75, 0x73, 0x65, 0x64, 0x42, 0x79, 0x74, 0x65, 0x73, 0x43, + 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x1b, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x73, 0x65, 0x64, 0x42, 0x79, 0x74, + 0x65, 0x73, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, + 0x52, 0x15, 0x75, 0x73, 0x65, 0x64, 0x42, 0x79, 0x74, 0x65, 0x73, 0x43, 0x68, 0x61, 0x6e, 0x67, + 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x50, 0x0a, 0x14, 0x69, 0x6d, 0x61, 0x70, 0x4c, + 0x6f, 0x67, 0x69, 0x6e, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x18, + 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6d, 0x61, + 0x70, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, + 0x74, 0x48, 0x00, 0x52, 0x14, 0x69, 0x6d, 0x61, 0x70, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x46, 0x61, + 0x69, 0x6c, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x44, 0x0a, 0x10, 0x73, 0x79, 0x6e, + 0x63, 0x53, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x18, 0x07, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x79, 0x6e, 0x63, 0x53, + 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x10, 0x73, + 0x79, 0x6e, 0x63, 0x53, 0x74, 0x61, 0x72, 0x74, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, + 0x47, 0x0a, 0x11, 0x73, 0x79, 0x6e, 0x63, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x45, + 0x76, 0x65, 0x6e, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x67, 0x72, 0x70, + 0x63, 0x2e, 0x53, 0x79, 0x6e, 0x63, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x45, 0x76, + 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x11, 0x73, 0x79, 0x6e, 0x63, 0x46, 0x69, 0x6e, 0x69, 0x73, + 0x68, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x47, 0x0a, 0x11, 0x73, 0x79, 0x6e, 0x63, + 0x50, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x18, 0x09, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x79, 0x6e, 0x63, 0x50, + 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x48, 0x00, 0x52, 0x11, + 0x73, 0x79, 0x6e, 0x63, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x45, 0x76, 0x65, 0x6e, + 0x74, 0x42, 0x07, 0x0a, 0x05, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x22, 0x36, 0x0a, 0x1c, 0x54, 0x6f, + 0x67, 0x67, 0x6c, 0x65, 0x53, 0x70, 0x6c, 0x69, 0x74, 0x4d, 0x6f, 0x64, 0x65, 0x46, 0x69, 0x6e, + 0x69, 0x73, 0x68, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x75, 0x73, + 0x65, 0x72, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, + 0x49, 0x44, 0x22, 0x33, 0x0a, 0x15, 0x55, 0x73, 0x65, 0x72, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x6e, + 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x75, + 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, + 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x2a, 0x0a, 0x10, 0x55, 0x73, 0x65, 0x72, 0x43, + 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, - 0x72, 0x49, 0x44, 0x22, 0x33, 0x0a, 0x15, 0x55, 0x73, 0x65, 0x72, 0x44, 0x69, 0x73, 0x63, 0x6f, - 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x1a, 0x0a, 0x08, - 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, - 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x2a, 0x0a, 0x10, 0x55, 0x73, 0x65, 0x72, - 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, - 0x75, 0x73, 0x65, 0x72, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, - 0x65, 0x72, 0x49, 0x44, 0x22, 0x4a, 0x0a, 0x0c, 0x55, 0x73, 0x65, 0x72, 0x42, 0x61, 0x64, 0x45, - 0x76, 0x65, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x44, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x44, 0x12, 0x22, 0x0a, 0x0c, - 0x65, 0x72, 0x72, 0x6f, 0x72, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0c, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, - 0x22, 0x38, 0x0a, 0x11, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x45, 0x72, 0x72, 0x6f, 0x72, - 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x23, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0e, 0x32, 0x0f, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, - 0x43, 0x6f, 0x64, 0x65, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x2a, 0x71, 0x0a, 0x08, 0x4c, 0x6f, - 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x0d, 0x0a, 0x09, 0x4c, 0x4f, 0x47, 0x5f, 0x50, 0x41, - 0x4e, 0x49, 0x43, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x4c, 0x4f, 0x47, 0x5f, 0x46, 0x41, 0x54, - 0x41, 0x4c, 0x10, 0x01, 0x12, 0x0d, 0x0a, 0x09, 0x4c, 0x4f, 0x47, 0x5f, 0x45, 0x52, 0x52, 0x4f, - 0x52, 0x10, 0x02, 0x12, 0x0c, 0x0a, 0x08, 0x4c, 0x4f, 0x47, 0x5f, 0x57, 0x41, 0x52, 0x4e, 0x10, - 0x03, 0x12, 0x0c, 0x0a, 0x08, 0x4c, 0x4f, 0x47, 0x5f, 0x49, 0x4e, 0x46, 0x4f, 0x10, 0x04, 0x12, - 0x0d, 0x0a, 0x09, 0x4c, 0x4f, 0x47, 0x5f, 0x44, 0x45, 0x42, 0x55, 0x47, 0x10, 0x05, 0x12, 0x0d, - 0x0a, 0x09, 0x4c, 0x4f, 0x47, 0x5f, 0x54, 0x52, 0x41, 0x43, 0x45, 0x10, 0x06, 0x2a, 0x36, 0x0a, - 0x09, 0x55, 0x73, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x0e, 0x0a, 0x0a, 0x53, 0x49, - 0x47, 0x4e, 0x45, 0x44, 0x5f, 0x4f, 0x55, 0x54, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x4c, 0x4f, - 0x43, 0x4b, 0x45, 0x44, 0x10, 0x01, 0x12, 0x0d, 0x0a, 0x09, 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, - 0x54, 0x45, 0x44, 0x10, 0x02, 0x2a, 0xa2, 0x01, 0x0a, 0x0e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x45, - 0x72, 0x72, 0x6f, 0x72, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1b, 0x0a, 0x17, 0x55, 0x53, 0x45, 0x52, - 0x4e, 0x41, 0x4d, 0x45, 0x5f, 0x50, 0x41, 0x53, 0x53, 0x57, 0x4f, 0x52, 0x44, 0x5f, 0x45, 0x52, - 0x52, 0x4f, 0x52, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x46, 0x52, 0x45, 0x45, 0x5f, 0x55, 0x53, - 0x45, 0x52, 0x10, 0x01, 0x12, 0x14, 0x0a, 0x10, 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x49, - 0x4f, 0x4e, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x02, 0x12, 0x0d, 0x0a, 0x09, 0x54, 0x46, - 0x41, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x03, 0x12, 0x0d, 0x0a, 0x09, 0x54, 0x46, 0x41, - 0x5f, 0x41, 0x42, 0x4f, 0x52, 0x54, 0x10, 0x04, 0x12, 0x17, 0x0a, 0x13, 0x54, 0x57, 0x4f, 0x5f, - 0x50, 0x41, 0x53, 0x53, 0x57, 0x4f, 0x52, 0x44, 0x53, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, - 0x05, 0x12, 0x17, 0x0a, 0x13, 0x54, 0x57, 0x4f, 0x5f, 0x50, 0x41, 0x53, 0x53, 0x57, 0x4f, 0x52, - 0x44, 0x53, 0x5f, 0x41, 0x42, 0x4f, 0x52, 0x54, 0x10, 0x06, 0x2a, 0x5b, 0x0a, 0x0f, 0x55, 0x70, - 0x64, 0x61, 0x74, 0x65, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x54, 0x79, 0x70, 0x65, 0x12, 0x17, 0x0a, - 0x13, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, 0x5f, 0x4d, 0x41, 0x4e, 0x55, 0x41, 0x4c, 0x5f, 0x45, - 0x52, 0x52, 0x4f, 0x52, 0x10, 0x00, 0x12, 0x16, 0x0a, 0x12, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, - 0x5f, 0x46, 0x4f, 0x52, 0x43, 0x45, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x01, 0x12, 0x17, - 0x0a, 0x13, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, 0x5f, 0x53, 0x49, 0x4c, 0x45, 0x4e, 0x54, 0x5f, - 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x02, 0x2a, 0x6b, 0x0a, 0x12, 0x44, 0x69, 0x73, 0x6b, 0x43, - 0x61, 0x63, 0x68, 0x65, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x54, 0x79, 0x70, 0x65, 0x12, 0x20, 0x0a, - 0x1c, 0x44, 0x49, 0x53, 0x4b, 0x5f, 0x43, 0x41, 0x43, 0x48, 0x45, 0x5f, 0x55, 0x4e, 0x41, 0x56, - 0x41, 0x49, 0x4c, 0x41, 0x42, 0x4c, 0x45, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x00, 0x12, - 0x1e, 0x0a, 0x1a, 0x43, 0x41, 0x4e, 0x54, 0x5f, 0x4d, 0x4f, 0x56, 0x45, 0x5f, 0x44, 0x49, 0x53, - 0x4b, 0x5f, 0x43, 0x41, 0x43, 0x48, 0x45, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x01, 0x12, - 0x13, 0x0a, 0x0f, 0x44, 0x49, 0x53, 0x4b, 0x5f, 0x46, 0x55, 0x4c, 0x4c, 0x5f, 0x45, 0x52, 0x52, - 0x4f, 0x52, 0x10, 0x02, 0x2a, 0xdd, 0x01, 0x0a, 0x1b, 0x4d, 0x61, 0x69, 0x6c, 0x53, 0x65, 0x72, - 0x76, 0x65, 0x72, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x45, 0x72, 0x72, 0x6f, 0x72, - 0x54, 0x79, 0x70, 0x65, 0x12, 0x1b, 0x0a, 0x17, 0x49, 0x4d, 0x41, 0x50, 0x5f, 0x50, 0x4f, 0x52, - 0x54, 0x5f, 0x53, 0x54, 0x41, 0x52, 0x54, 0x55, 0x50, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, - 0x00, 0x12, 0x1b, 0x0a, 0x17, 0x53, 0x4d, 0x54, 0x50, 0x5f, 0x50, 0x4f, 0x52, 0x54, 0x5f, 0x53, - 0x54, 0x41, 0x52, 0x54, 0x55, 0x50, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x01, 0x12, 0x1a, - 0x0a, 0x16, 0x49, 0x4d, 0x41, 0x50, 0x5f, 0x50, 0x4f, 0x52, 0x54, 0x5f, 0x43, 0x48, 0x41, 0x4e, - 0x47, 0x45, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x02, 0x12, 0x1a, 0x0a, 0x16, 0x53, 0x4d, - 0x54, 0x50, 0x5f, 0x50, 0x4f, 0x52, 0x54, 0x5f, 0x43, 0x48, 0x41, 0x4e, 0x47, 0x45, 0x5f, 0x45, - 0x52, 0x52, 0x4f, 0x52, 0x10, 0x03, 0x12, 0x25, 0x0a, 0x21, 0x49, 0x4d, 0x41, 0x50, 0x5f, 0x43, - 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x4d, 0x4f, 0x44, 0x45, 0x5f, 0x43, - 0x48, 0x41, 0x4e, 0x47, 0x45, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x04, 0x12, 0x25, 0x0a, - 0x21, 0x53, 0x4d, 0x54, 0x50, 0x5f, 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x49, 0x4f, 0x4e, - 0x5f, 0x4d, 0x4f, 0x44, 0x45, 0x5f, 0x43, 0x48, 0x41, 0x4e, 0x47, 0x45, 0x5f, 0x45, 0x52, 0x52, - 0x4f, 0x52, 0x10, 0x05, 0x2a, 0x53, 0x0a, 0x09, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x43, 0x6f, 0x64, - 0x65, 0x12, 0x11, 0x0a, 0x0d, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x5f, 0x45, 0x52, 0x52, - 0x4f, 0x52, 0x10, 0x00, 0x12, 0x19, 0x0a, 0x15, 0x54, 0x4c, 0x53, 0x5f, 0x43, 0x45, 0x52, 0x54, - 0x5f, 0x45, 0x58, 0x50, 0x4f, 0x52, 0x54, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x01, 0x12, - 0x18, 0x0a, 0x14, 0x54, 0x4c, 0x53, 0x5f, 0x4b, 0x45, 0x59, 0x5f, 0x45, 0x58, 0x50, 0x4f, 0x52, - 0x54, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x02, 0x32, 0xf0, 0x1d, 0x0a, 0x06, 0x42, 0x72, - 0x69, 0x64, 0x67, 0x65, 0x12, 0x49, 0x0a, 0x0b, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x54, 0x6f, 0x6b, - 0x65, 0x6e, 0x73, 0x12, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, - 0x65, 0x1a, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, - 0x3f, 0x0a, 0x0b, 0x41, 0x64, 0x64, 0x4c, 0x6f, 0x67, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x18, - 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x4c, 0x6f, 0x67, 0x45, 0x6e, 0x74, 0x72, - 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, - 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, - 0x12, 0x3a, 0x0a, 0x08, 0x47, 0x75, 0x69, 0x52, 0x65, 0x61, 0x64, 0x79, 0x12, 0x16, 0x2e, 0x67, - 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, - 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x16, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x47, 0x75, 0x69, 0x52, - 0x65, 0x61, 0x64, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x36, 0x0a, 0x04, - 0x51, 0x75, 0x69, 0x74, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, + 0x72, 0x49, 0x44, 0x22, 0x4a, 0x0a, 0x0c, 0x55, 0x73, 0x65, 0x72, 0x42, 0x61, 0x64, 0x45, 0x76, + 0x65, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x44, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x44, 0x12, 0x22, 0x0a, 0x0c, 0x65, + 0x72, 0x72, 0x6f, 0x72, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0c, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, + 0x4d, 0x0a, 0x15, 0x55, 0x73, 0x65, 0x64, 0x42, 0x79, 0x74, 0x65, 0x73, 0x43, 0x68, 0x61, 0x6e, + 0x67, 0x65, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x75, 0x73, 0x65, 0x72, + 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x44, + 0x12, 0x1c, 0x0a, 0x09, 0x75, 0x73, 0x65, 0x64, 0x42, 0x79, 0x74, 0x65, 0x73, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x09, 0x75, 0x73, 0x65, 0x64, 0x42, 0x79, 0x74, 0x65, 0x73, 0x22, 0x32, + 0x0a, 0x14, 0x49, 0x6d, 0x61, 0x70, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x46, 0x61, 0x69, 0x6c, 0x65, + 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, + 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x75, 0x73, 0x65, 0x72, 0x6e, 0x61, + 0x6d, 0x65, 0x22, 0x2a, 0x0a, 0x10, 0x53, 0x79, 0x6e, 0x63, 0x53, 0x74, 0x61, 0x72, 0x74, 0x65, + 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x44, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x44, 0x22, 0x2b, + 0x0a, 0x11, 0x53, 0x79, 0x6e, 0x63, 0x46, 0x69, 0x6e, 0x69, 0x73, 0x68, 0x65, 0x64, 0x45, 0x76, + 0x65, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x44, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x44, 0x22, 0x87, 0x01, 0x0a, 0x11, + 0x53, 0x79, 0x6e, 0x63, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x45, 0x76, 0x65, 0x6e, + 0x74, 0x12, 0x16, 0x0a, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x44, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x6f, + 0x67, 0x72, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x52, 0x08, 0x70, 0x72, 0x6f, + 0x67, 0x72, 0x65, 0x73, 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x65, 0x6c, 0x61, 0x70, 0x73, 0x65, 0x64, + 0x4d, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x65, 0x6c, 0x61, 0x70, 0x73, 0x65, + 0x64, 0x4d, 0x73, 0x12, 0x20, 0x0a, 0x0b, 0x72, 0x65, 0x6d, 0x61, 0x69, 0x6e, 0x69, 0x6e, 0x67, + 0x4d, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0b, 0x72, 0x65, 0x6d, 0x61, 0x69, 0x6e, + 0x69, 0x6e, 0x67, 0x4d, 0x73, 0x22, 0x38, 0x0a, 0x11, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, + 0x45, 0x72, 0x72, 0x6f, 0x72, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x23, 0x0a, 0x04, 0x63, 0x6f, + 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0f, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, + 0x45, 0x72, 0x72, 0x6f, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x2a, + 0x71, 0x0a, 0x08, 0x4c, 0x6f, 0x67, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x0d, 0x0a, 0x09, 0x4c, + 0x4f, 0x47, 0x5f, 0x50, 0x41, 0x4e, 0x49, 0x43, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x4c, 0x4f, + 0x47, 0x5f, 0x46, 0x41, 0x54, 0x41, 0x4c, 0x10, 0x01, 0x12, 0x0d, 0x0a, 0x09, 0x4c, 0x4f, 0x47, + 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x02, 0x12, 0x0c, 0x0a, 0x08, 0x4c, 0x4f, 0x47, 0x5f, + 0x57, 0x41, 0x52, 0x4e, 0x10, 0x03, 0x12, 0x0c, 0x0a, 0x08, 0x4c, 0x4f, 0x47, 0x5f, 0x49, 0x4e, + 0x46, 0x4f, 0x10, 0x04, 0x12, 0x0d, 0x0a, 0x09, 0x4c, 0x4f, 0x47, 0x5f, 0x44, 0x45, 0x42, 0x55, + 0x47, 0x10, 0x05, 0x12, 0x0d, 0x0a, 0x09, 0x4c, 0x4f, 0x47, 0x5f, 0x54, 0x52, 0x41, 0x43, 0x45, + 0x10, 0x06, 0x2a, 0x36, 0x0a, 0x09, 0x55, 0x73, 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, + 0x0e, 0x0a, 0x0a, 0x53, 0x49, 0x47, 0x4e, 0x45, 0x44, 0x5f, 0x4f, 0x55, 0x54, 0x10, 0x00, 0x12, + 0x0a, 0x0a, 0x06, 0x4c, 0x4f, 0x43, 0x4b, 0x45, 0x44, 0x10, 0x01, 0x12, 0x0d, 0x0a, 0x09, 0x43, + 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x45, 0x44, 0x10, 0x02, 0x2a, 0xa2, 0x01, 0x0a, 0x0e, 0x4c, + 0x6f, 0x67, 0x69, 0x6e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1b, 0x0a, + 0x17, 0x55, 0x53, 0x45, 0x52, 0x4e, 0x41, 0x4d, 0x45, 0x5f, 0x50, 0x41, 0x53, 0x53, 0x57, 0x4f, + 0x52, 0x44, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x46, 0x52, + 0x45, 0x45, 0x5f, 0x55, 0x53, 0x45, 0x52, 0x10, 0x01, 0x12, 0x14, 0x0a, 0x10, 0x43, 0x4f, 0x4e, + 0x4e, 0x45, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x02, 0x12, + 0x0d, 0x0a, 0x09, 0x54, 0x46, 0x41, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x03, 0x12, 0x0d, + 0x0a, 0x09, 0x54, 0x46, 0x41, 0x5f, 0x41, 0x42, 0x4f, 0x52, 0x54, 0x10, 0x04, 0x12, 0x17, 0x0a, + 0x13, 0x54, 0x57, 0x4f, 0x5f, 0x50, 0x41, 0x53, 0x53, 0x57, 0x4f, 0x52, 0x44, 0x53, 0x5f, 0x45, + 0x52, 0x52, 0x4f, 0x52, 0x10, 0x05, 0x12, 0x17, 0x0a, 0x13, 0x54, 0x57, 0x4f, 0x5f, 0x50, 0x41, + 0x53, 0x53, 0x57, 0x4f, 0x52, 0x44, 0x53, 0x5f, 0x41, 0x42, 0x4f, 0x52, 0x54, 0x10, 0x06, 0x2a, + 0x5b, 0x0a, 0x0f, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x54, 0x79, + 0x70, 0x65, 0x12, 0x17, 0x0a, 0x13, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, 0x5f, 0x4d, 0x41, 0x4e, + 0x55, 0x41, 0x4c, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x00, 0x12, 0x16, 0x0a, 0x12, 0x55, + 0x50, 0x44, 0x41, 0x54, 0x45, 0x5f, 0x46, 0x4f, 0x52, 0x43, 0x45, 0x5f, 0x45, 0x52, 0x52, 0x4f, + 0x52, 0x10, 0x01, 0x12, 0x17, 0x0a, 0x13, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, 0x5f, 0x53, 0x49, + 0x4c, 0x45, 0x4e, 0x54, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x02, 0x2a, 0x6b, 0x0a, 0x12, + 0x44, 0x69, 0x73, 0x6b, 0x43, 0x61, 0x63, 0x68, 0x65, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x54, 0x79, + 0x70, 0x65, 0x12, 0x20, 0x0a, 0x1c, 0x44, 0x49, 0x53, 0x4b, 0x5f, 0x43, 0x41, 0x43, 0x48, 0x45, + 0x5f, 0x55, 0x4e, 0x41, 0x56, 0x41, 0x49, 0x4c, 0x41, 0x42, 0x4c, 0x45, 0x5f, 0x45, 0x52, 0x52, + 0x4f, 0x52, 0x10, 0x00, 0x12, 0x1e, 0x0a, 0x1a, 0x43, 0x41, 0x4e, 0x54, 0x5f, 0x4d, 0x4f, 0x56, + 0x45, 0x5f, 0x44, 0x49, 0x53, 0x4b, 0x5f, 0x43, 0x41, 0x43, 0x48, 0x45, 0x5f, 0x45, 0x52, 0x52, + 0x4f, 0x52, 0x10, 0x01, 0x12, 0x13, 0x0a, 0x0f, 0x44, 0x49, 0x53, 0x4b, 0x5f, 0x46, 0x55, 0x4c, + 0x4c, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x02, 0x2a, 0xdd, 0x01, 0x0a, 0x1b, 0x4d, 0x61, + 0x69, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, + 0x45, 0x72, 0x72, 0x6f, 0x72, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1b, 0x0a, 0x17, 0x49, 0x4d, 0x41, + 0x50, 0x5f, 0x50, 0x4f, 0x52, 0x54, 0x5f, 0x53, 0x54, 0x41, 0x52, 0x54, 0x55, 0x50, 0x5f, 0x45, + 0x52, 0x52, 0x4f, 0x52, 0x10, 0x00, 0x12, 0x1b, 0x0a, 0x17, 0x53, 0x4d, 0x54, 0x50, 0x5f, 0x50, + 0x4f, 0x52, 0x54, 0x5f, 0x53, 0x54, 0x41, 0x52, 0x54, 0x55, 0x50, 0x5f, 0x45, 0x52, 0x52, 0x4f, + 0x52, 0x10, 0x01, 0x12, 0x1a, 0x0a, 0x16, 0x49, 0x4d, 0x41, 0x50, 0x5f, 0x50, 0x4f, 0x52, 0x54, + 0x5f, 0x43, 0x48, 0x41, 0x4e, 0x47, 0x45, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x02, 0x12, + 0x1a, 0x0a, 0x16, 0x53, 0x4d, 0x54, 0x50, 0x5f, 0x50, 0x4f, 0x52, 0x54, 0x5f, 0x43, 0x48, 0x41, + 0x4e, 0x47, 0x45, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x03, 0x12, 0x25, 0x0a, 0x21, 0x49, + 0x4d, 0x41, 0x50, 0x5f, 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x4d, + 0x4f, 0x44, 0x45, 0x5f, 0x43, 0x48, 0x41, 0x4e, 0x47, 0x45, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, + 0x10, 0x04, 0x12, 0x25, 0x0a, 0x21, 0x53, 0x4d, 0x54, 0x50, 0x5f, 0x43, 0x4f, 0x4e, 0x4e, 0x45, + 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x4d, 0x4f, 0x44, 0x45, 0x5f, 0x43, 0x48, 0x41, 0x4e, 0x47, + 0x45, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x05, 0x2a, 0x53, 0x0a, 0x09, 0x45, 0x72, 0x72, + 0x6f, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x11, 0x0a, 0x0d, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, + 0x4e, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x00, 0x12, 0x19, 0x0a, 0x15, 0x54, 0x4c, 0x53, + 0x5f, 0x43, 0x45, 0x52, 0x54, 0x5f, 0x45, 0x58, 0x50, 0x4f, 0x52, 0x54, 0x5f, 0x45, 0x52, 0x52, + 0x4f, 0x52, 0x10, 0x01, 0x12, 0x18, 0x0a, 0x14, 0x54, 0x4c, 0x53, 0x5f, 0x4b, 0x45, 0x59, 0x5f, + 0x45, 0x58, 0x50, 0x4f, 0x52, 0x54, 0x5f, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x02, 0x32, 0xf0, + 0x1d, 0x0a, 0x06, 0x42, 0x72, 0x69, 0x64, 0x67, 0x65, 0x12, 0x49, 0x0a, 0x0b, 0x43, 0x68, 0x65, + 0x63, 0x6b, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x12, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, + 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, + 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, + 0x61, 0x6c, 0x75, 0x65, 0x12, 0x3f, 0x0a, 0x0b, 0x41, 0x64, 0x64, 0x4c, 0x6f, 0x67, 0x45, 0x6e, + 0x74, 0x72, 0x79, 0x12, 0x18, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x64, 0x64, 0x4c, 0x6f, + 0x67, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, + 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, + 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x3a, 0x0a, 0x08, 0x47, 0x75, 0x69, 0x52, 0x65, 0x61, 0x64, + 0x79, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x16, 0x2e, 0x67, 0x72, 0x70, 0x63, + 0x2e, 0x47, 0x75, 0x69, 0x52, 0x65, 0x61, 0x64, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x36, 0x0a, 0x04, 0x51, 0x75, 0x69, 0x74, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, + 0x79, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x39, 0x0a, 0x07, 0x52, 0x65, 0x73, + 0x74, 0x61, 0x72, 0x74, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, - 0x6d, 0x70, 0x74, 0x79, 0x12, 0x39, 0x0a, 0x07, 0x52, 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x12, - 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, - 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, - 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, - 0x43, 0x0a, 0x0d, 0x53, 0x68, 0x6f, 0x77, 0x4f, 0x6e, 0x53, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, - 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, - 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, - 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x42, 0x6f, 0x6f, 0x6c, 0x56, - 0x61, 0x6c, 0x75, 0x65, 0x12, 0x46, 0x0a, 0x10, 0x53, 0x65, 0x74, 0x49, 0x73, 0x41, 0x75, 0x74, - 0x6f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x4f, 0x6e, 0x12, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, - 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x42, 0x6f, 0x6f, 0x6c, 0x56, - 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x43, 0x0a, 0x0d, - 0x49, 0x73, 0x41, 0x75, 0x74, 0x6f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x4f, 0x6e, 0x12, 0x16, 0x2e, + 0x6d, 0x70, 0x74, 0x79, 0x12, 0x43, 0x0a, 0x0d, 0x53, 0x68, 0x6f, 0x77, 0x4f, 0x6e, 0x53, 0x74, + 0x61, 0x72, 0x74, 0x75, 0x70, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, - 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x42, 0x6f, 0x6f, 0x6c, 0x56, 0x61, 0x6c, 0x75, - 0x65, 0x12, 0x46, 0x0a, 0x10, 0x53, 0x65, 0x74, 0x49, 0x73, 0x42, 0x65, 0x74, 0x61, 0x45, 0x6e, - 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x42, 0x6f, 0x6f, 0x6c, 0x56, 0x61, 0x6c, 0x75, - 0x65, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x43, 0x0a, 0x0d, 0x49, 0x73, 0x42, - 0x65, 0x74, 0x61, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, - 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, - 0x74, 0x79, 0x1a, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x42, 0x6f, 0x6f, 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x49, - 0x0a, 0x13, 0x53, 0x65, 0x74, 0x49, 0x73, 0x41, 0x6c, 0x6c, 0x4d, 0x61, 0x69, 0x6c, 0x56, 0x69, - 0x73, 0x69, 0x62, 0x6c, 0x65, 0x12, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x42, 0x6f, 0x6f, 0x6c, 0x56, 0x61, 0x6c, 0x75, - 0x65, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x46, 0x0a, 0x10, 0x49, 0x73, 0x41, - 0x6c, 0x6c, 0x4d, 0x61, 0x69, 0x6c, 0x56, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x12, 0x16, 0x2e, + 0x42, 0x6f, 0x6f, 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x46, 0x0a, 0x10, 0x53, 0x65, 0x74, + 0x49, 0x73, 0x41, 0x75, 0x74, 0x6f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x4f, 0x6e, 0x12, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, - 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x42, 0x6f, 0x6f, 0x6c, 0x56, 0x61, 0x6c, 0x75, - 0x65, 0x12, 0x3c, 0x0a, 0x04, 0x47, 0x6f, 0x4f, 0x73, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, + 0x42, 0x6f, 0x6f, 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, - 0x79, 0x1a, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, - 0x3e, 0x0a, 0x0c, 0x54, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x52, 0x65, 0x73, 0x65, 0x74, 0x12, + 0x79, 0x12, 0x43, 0x0a, 0x0d, 0x49, 0x73, 0x41, 0x75, 0x74, 0x6f, 0x73, 0x74, 0x61, 0x72, 0x74, + 0x4f, 0x6e, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x42, 0x6f, 0x6f, + 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x46, 0x0a, 0x10, 0x53, 0x65, 0x74, 0x49, 0x73, 0x42, + 0x65, 0x74, 0x61, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x42, 0x6f, 0x6f, + 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x43, + 0x0a, 0x0d, 0x49, 0x73, 0x42, 0x65, 0x74, 0x61, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, - 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, - 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, - 0x3f, 0x0a, 0x07, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, - 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, - 0x74, 0x79, 0x1a, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, - 0x12, 0x40, 0x0a, 0x08, 0x4c, 0x6f, 0x67, 0x73, 0x50, 0x61, 0x74, 0x68, 0x12, 0x16, 0x2e, 0x67, + 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x42, 0x6f, 0x6f, 0x6c, 0x56, 0x61, + 0x6c, 0x75, 0x65, 0x12, 0x49, 0x0a, 0x13, 0x53, 0x65, 0x74, 0x49, 0x73, 0x41, 0x6c, 0x6c, 0x4d, + 0x61, 0x69, 0x6c, 0x56, 0x69, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x12, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x42, 0x6f, 0x6f, + 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x46, + 0x0a, 0x10, 0x49, 0x73, 0x41, 0x6c, 0x6c, 0x4d, 0x61, 0x69, 0x6c, 0x56, 0x69, 0x73, 0x69, 0x62, + 0x6c, 0x65, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x42, 0x6f, 0x6f, + 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x3c, 0x0a, 0x04, 0x47, 0x6f, 0x4f, 0x73, 0x12, 0x16, + 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, + 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, + 0x61, 0x6c, 0x75, 0x65, 0x12, 0x3e, 0x0a, 0x0c, 0x54, 0x72, 0x69, 0x67, 0x67, 0x65, 0x72, 0x52, + 0x65, 0x73, 0x65, 0x74, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, - 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, - 0x75, 0x65, 0x12, 0x43, 0x0a, 0x0b, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x50, 0x61, 0x74, + 0x6d, 0x70, 0x74, 0x79, 0x12, 0x3f, 0x0a, 0x07, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, + 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, + 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, + 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x40, 0x0a, 0x08, 0x4c, 0x6f, 0x67, 0x73, 0x50, 0x61, 0x74, 0x68, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, - 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x4c, 0x0a, 0x14, 0x52, 0x65, 0x6c, 0x65, 0x61, - 0x73, 0x65, 0x4e, 0x6f, 0x74, 0x65, 0x73, 0x50, 0x61, 0x67, 0x65, 0x4c, 0x69, 0x6e, 0x6b, 0x12, - 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, - 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, - 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, - 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x4e, 0x0a, 0x16, 0x44, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x65, - 0x6e, 0x63, 0x79, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x73, 0x4c, 0x69, 0x6e, 0x6b, 0x12, - 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, - 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, - 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, - 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x47, 0x0a, 0x0f, 0x4c, 0x61, 0x6e, 0x64, 0x69, 0x6e, 0x67, - 0x50, 0x61, 0x67, 0x65, 0x4c, 0x69, 0x6e, 0x6b, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, - 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, - 0x1a, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, - 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x4a, - 0x0a, 0x12, 0x53, 0x65, 0x74, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x65, - 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, - 0x75, 0x65, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x47, 0x0a, 0x0f, 0x43, 0x6f, - 0x6c, 0x6f, 0x72, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x2e, + 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x43, 0x0a, 0x0b, 0x4c, 0x69, 0x63, 0x65, 0x6e, + 0x73, 0x65, 0x50, 0x61, 0x74, 0x68, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1c, + 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, + 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x4c, 0x0a, 0x14, + 0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x4e, 0x6f, 0x74, 0x65, 0x73, 0x50, 0x61, 0x67, 0x65, + 0x4c, 0x69, 0x6e, 0x6b, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1c, 0x2e, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, + 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x4e, 0x0a, 0x16, 0x44, 0x65, + 0x70, 0x65, 0x6e, 0x64, 0x65, 0x6e, 0x63, 0x79, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x73, + 0x4c, 0x69, 0x6e, 0x6b, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1c, 0x2e, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, + 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x47, 0x0a, 0x0f, 0x4c, 0x61, + 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x50, 0x61, 0x67, 0x65, 0x4c, 0x69, 0x6e, 0x6b, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, - 0x6c, 0x75, 0x65, 0x12, 0x4a, 0x0a, 0x12, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x45, 0x6d, - 0x61, 0x69, 0x6c, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, + 0x6c, 0x75, 0x65, 0x12, 0x4a, 0x0a, 0x12, 0x53, 0x65, 0x74, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x53, + 0x63, 0x68, 0x65, 0x6d, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, + 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, + 0x47, 0x0a, 0x0f, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x65, 0x4e, 0x61, + 0x6d, 0x65, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, + 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x4a, 0x0a, 0x12, 0x43, 0x75, 0x72, 0x72, + 0x65, 0x6e, 0x74, 0x45, 0x6d, 0x61, 0x69, 0x6c, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x12, 0x16, + 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, + 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, + 0x61, 0x6c, 0x75, 0x65, 0x12, 0x3b, 0x0a, 0x09, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x42, 0x75, + 0x67, 0x12, 0x16, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x42, + 0x75, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, + 0x79, 0x12, 0x4d, 0x0a, 0x15, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x54, 0x4c, 0x53, 0x43, 0x65, + 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, 0x12, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, + 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, + 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, + 0x12, 0x45, 0x0a, 0x0d, 0x46, 0x6f, 0x72, 0x63, 0x65, 0x4c, 0x61, 0x75, 0x6e, 0x63, 0x68, 0x65, + 0x72, 0x12, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, + 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, + 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x49, 0x0a, 0x11, 0x53, 0x65, 0x74, 0x4d, 0x61, + 0x69, 0x6e, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x1c, 0x2e, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, + 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, + 0x74, 0x79, 0x12, 0x33, 0x0a, 0x05, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x12, 0x2e, 0x67, 0x72, + 0x70, 0x63, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, + 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x36, 0x0a, 0x08, 0x4c, 0x6f, 0x67, 0x69, 0x6e, + 0x32, 0x46, 0x41, 0x12, 0x12, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, + 0x3d, 0x0a, 0x0f, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x32, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, + 0x64, 0x73, 0x12, 0x12, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x3d, + 0x0a, 0x0a, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x41, 0x62, 0x6f, 0x72, 0x74, 0x12, 0x17, 0x2e, 0x67, + 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x41, 0x62, 0x6f, 0x72, 0x74, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x3d, 0x0a, + 0x0b, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x16, 0x2e, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, + 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x3f, 0x0a, 0x0d, + 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x16, 0x2e, + 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, + 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x4c, 0x0a, + 0x16, 0x53, 0x65, 0x74, 0x49, 0x73, 0x41, 0x75, 0x74, 0x6f, 0x6d, 0x61, 0x74, 0x69, 0x63, 0x55, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x4f, 0x6e, 0x12, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x42, 0x6f, 0x6f, 0x6c, 0x56, 0x61, + 0x6c, 0x75, 0x65, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x49, 0x0a, 0x13, 0x49, + 0x73, 0x41, 0x75, 0x74, 0x6f, 0x6d, 0x61, 0x74, 0x69, 0x63, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x4f, 0x6e, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x42, 0x6f, 0x6f, + 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x45, 0x0a, 0x0d, 0x44, 0x69, 0x73, 0x6b, 0x43, 0x61, + 0x63, 0x68, 0x65, 0x50, 0x61, 0x74, 0x68, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, + 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, + 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x48, 0x0a, + 0x10, 0x53, 0x65, 0x74, 0x44, 0x69, 0x73, 0x6b, 0x43, 0x61, 0x63, 0x68, 0x65, 0x50, 0x61, 0x74, + 0x68, 0x12, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, + 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, + 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x45, 0x0a, 0x0f, 0x53, 0x65, 0x74, 0x49, 0x73, + 0x44, 0x6f, 0x48, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x42, 0x6f, 0x6f, + 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x42, + 0x0a, 0x0c, 0x49, 0x73, 0x44, 0x6f, 0x48, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x16, + 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, + 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x42, 0x6f, 0x6f, 0x6c, 0x56, 0x61, 0x6c, + 0x75, 0x65, 0x12, 0x44, 0x0a, 0x12, 0x4d, 0x61, 0x69, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, + 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, + 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, + 0x1a, 0x16, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6d, 0x61, 0x70, 0x53, 0x6d, 0x74, 0x70, + 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x47, 0x0a, 0x15, 0x53, 0x65, 0x74, 0x4d, + 0x61, 0x69, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, + 0x73, 0x12, 0x16, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6d, 0x61, 0x70, 0x53, 0x6d, 0x74, + 0x70, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, + 0x79, 0x12, 0x40, 0x0a, 0x08, 0x48, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x2e, + 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, + 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, + 0x6c, 0x75, 0x65, 0x12, 0x45, 0x0a, 0x0a, 0x49, 0x73, 0x50, 0x6f, 0x72, 0x74, 0x46, 0x72, 0x65, + 0x65, 0x12, 0x1b, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x62, 0x75, 0x66, 0x2e, 0x49, 0x6e, 0x74, 0x33, 0x32, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x1a, + 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, + 0x2e, 0x42, 0x6f, 0x6f, 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x4e, 0x0a, 0x12, 0x41, 0x76, + 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x4b, 0x65, 0x79, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x73, + 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, + 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x20, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, + 0x41, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x4b, 0x65, 0x79, 0x63, 0x68, 0x61, 0x69, + 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4a, 0x0a, 0x12, 0x53, 0x65, + 0x74, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x4b, 0x65, 0x79, 0x63, 0x68, 0x61, 0x69, 0x6e, + 0x12, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, + 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x16, + 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, + 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x47, 0x0a, 0x0f, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, + 0x74, 0x4b, 0x65, 0x79, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, - 0x3b, 0x0a, 0x09, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x42, 0x75, 0x67, 0x12, 0x16, 0x2e, 0x67, - 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x42, 0x75, 0x67, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x4d, 0x0a, 0x15, - 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x54, 0x4c, 0x53, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, - 0x63, 0x61, 0x74, 0x65, 0x73, 0x12, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, - 0x6c, 0x75, 0x65, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x45, 0x0a, 0x0d, 0x46, - 0x6f, 0x72, 0x63, 0x65, 0x4c, 0x61, 0x75, 0x6e, 0x63, 0x68, 0x65, 0x72, 0x12, 0x1c, 0x2e, 0x67, - 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, - 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, - 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, - 0x74, 0x79, 0x12, 0x49, 0x0a, 0x11, 0x53, 0x65, 0x74, 0x4d, 0x61, 0x69, 0x6e, 0x45, 0x78, 0x65, - 0x63, 0x75, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, - 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, - 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x33, 0x0a, - 0x05, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x12, 0x12, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x6f, - 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, - 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, - 0x74, 0x79, 0x12, 0x36, 0x0a, 0x08, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x32, 0x46, 0x41, 0x12, 0x12, - 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x3d, 0x0a, 0x0f, 0x4c, 0x6f, - 0x67, 0x69, 0x6e, 0x32, 0x50, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x73, 0x12, 0x12, 0x2e, - 0x67, 0x72, 0x70, 0x63, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x3d, 0x0a, 0x0a, 0x4c, 0x6f, 0x67, - 0x69, 0x6e, 0x41, 0x62, 0x6f, 0x72, 0x74, 0x12, 0x17, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x4c, - 0x6f, 0x67, 0x69, 0x6e, 0x41, 0x62, 0x6f, 0x72, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, - 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x3d, 0x0a, 0x0b, 0x43, 0x68, 0x65, 0x63, - 0x6b, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, - 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, - 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, - 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x3f, 0x0a, 0x0d, 0x49, 0x6e, 0x73, 0x74, 0x61, - 0x6c, 0x6c, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, - 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, - 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, - 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x4c, 0x0a, 0x16, 0x53, 0x65, 0x74, 0x49, - 0x73, 0x41, 0x75, 0x74, 0x6f, 0x6d, 0x61, 0x74, 0x69, 0x63, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, - 0x4f, 0x6e, 0x12, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x42, 0x6f, 0x6f, 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x16, + 0x3d, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, - 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x49, 0x0a, 0x13, 0x49, 0x73, 0x41, 0x75, 0x74, 0x6f, - 0x6d, 0x61, 0x74, 0x69, 0x63, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x4f, 0x6e, 0x12, 0x16, 0x2e, - 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, - 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x42, 0x6f, 0x6f, 0x6c, 0x56, 0x61, 0x6c, 0x75, - 0x65, 0x12, 0x45, 0x0a, 0x0d, 0x44, 0x69, 0x73, 0x6b, 0x43, 0x61, 0x63, 0x68, 0x65, 0x50, 0x61, - 0x74, 0x68, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, - 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, - 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x48, 0x0a, 0x10, 0x53, 0x65, 0x74, 0x44, - 0x69, 0x73, 0x6b, 0x43, 0x61, 0x63, 0x68, 0x65, 0x50, 0x61, 0x74, 0x68, 0x12, 0x1c, 0x2e, 0x67, - 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, - 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, + 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x16, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x73, + 0x65, 0x72, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x33, + 0x0a, 0x07, 0x47, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x12, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, + 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x0a, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x55, + 0x73, 0x65, 0x72, 0x12, 0x46, 0x0a, 0x10, 0x53, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x53, 0x70, + 0x6c, 0x69, 0x74, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x1a, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x55, + 0x73, 0x65, 0x72, 0x53, 0x70, 0x6c, 0x69, 0x74, 0x4d, 0x6f, 0x64, 0x65, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x55, 0x0a, 0x18, 0x53, + 0x65, 0x6e, 0x64, 0x42, 0x61, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x55, 0x73, 0x65, 0x72, 0x46, + 0x65, 0x65, 0x64, 0x62, 0x61, 0x63, 0x6b, 0x12, 0x21, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x55, + 0x73, 0x65, 0x72, 0x42, 0x61, 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x46, 0x65, 0x65, 0x64, 0x62, + 0x61, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, - 0x74, 0x79, 0x12, 0x45, 0x0a, 0x0f, 0x53, 0x65, 0x74, 0x49, 0x73, 0x44, 0x6f, 0x48, 0x45, 0x6e, - 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x42, 0x6f, 0x6f, 0x6c, 0x56, 0x61, 0x6c, 0x75, - 0x65, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x42, 0x0a, 0x0c, 0x49, 0x73, 0x44, - 0x6f, 0x48, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, - 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, - 0x79, 0x1a, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x62, 0x75, 0x66, 0x2e, 0x42, 0x6f, 0x6f, 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x44, 0x0a, - 0x12, 0x4d, 0x61, 0x69, 0x6c, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x65, 0x74, 0x74, 0x69, - 0x6e, 0x67, 0x73, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x16, 0x2e, 0x67, 0x72, - 0x70, 0x63, 0x2e, 0x49, 0x6d, 0x61, 0x70, 0x53, 0x6d, 0x74, 0x70, 0x53, 0x65, 0x74, 0x74, 0x69, - 0x6e, 0x67, 0x73, 0x12, 0x47, 0x0a, 0x15, 0x53, 0x65, 0x74, 0x4d, 0x61, 0x69, 0x6c, 0x53, 0x65, - 0x72, 0x76, 0x65, 0x72, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x16, 0x2e, 0x67, - 0x72, 0x70, 0x63, 0x2e, 0x49, 0x6d, 0x61, 0x70, 0x53, 0x6d, 0x74, 0x70, 0x53, 0x65, 0x74, 0x74, - 0x69, 0x6e, 0x67, 0x73, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x40, 0x0a, 0x08, - 0x48, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, - 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, - 0x1a, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, - 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x45, - 0x0a, 0x0a, 0x49, 0x73, 0x50, 0x6f, 0x72, 0x74, 0x46, 0x72, 0x65, 0x65, 0x12, 0x1b, 0x2e, 0x67, - 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x49, - 0x6e, 0x74, 0x33, 0x32, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, - 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x42, 0x6f, 0x6f, 0x6c, - 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x4e, 0x0a, 0x12, 0x41, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, - 0x6c, 0x65, 0x4b, 0x65, 0x79, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x73, 0x12, 0x16, 0x2e, 0x67, 0x6f, - 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, - 0x70, 0x74, 0x79, 0x1a, 0x20, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x41, 0x76, 0x61, 0x69, 0x6c, - 0x61, 0x62, 0x6c, 0x65, 0x4b, 0x65, 0x79, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x73, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4a, 0x0a, 0x12, 0x53, 0x65, 0x74, 0x43, 0x75, 0x72, 0x72, - 0x65, 0x6e, 0x74, 0x4b, 0x65, 0x79, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x12, 0x1c, 0x2e, 0x67, 0x6f, - 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, - 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, - 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, - 0x79, 0x12, 0x47, 0x0a, 0x0f, 0x43, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x4b, 0x65, 0x79, 0x63, - 0x68, 0x61, 0x69, 0x6e, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1c, 0x2e, 0x67, - 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, - 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x3d, 0x0a, 0x0b, 0x47, 0x65, - 0x74, 0x55, 0x73, 0x65, 0x72, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, - 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, - 0x79, 0x1a, 0x16, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x4c, 0x69, 0x73, - 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x33, 0x0a, 0x07, 0x47, 0x65, 0x74, + 0x74, 0x79, 0x12, 0x42, 0x0a, 0x0a, 0x4c, 0x6f, 0x67, 0x6f, 0x75, 0x74, 0x55, 0x73, 0x65, 0x72, + 0x12, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, + 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x16, + 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, + 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x42, 0x0a, 0x0a, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x55, 0x73, 0x65, 0x72, 0x12, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, - 0x75, 0x65, 0x1a, 0x0a, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x12, 0x46, - 0x0a, 0x10, 0x53, 0x65, 0x74, 0x55, 0x73, 0x65, 0x72, 0x53, 0x70, 0x6c, 0x69, 0x74, 0x4d, 0x6f, - 0x64, 0x65, 0x12, 0x1a, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x53, 0x70, - 0x6c, 0x69, 0x74, 0x4d, 0x6f, 0x64, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, - 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, - 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x55, 0x0a, 0x18, 0x53, 0x65, 0x6e, 0x64, 0x42, 0x61, - 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x55, 0x73, 0x65, 0x72, 0x46, 0x65, 0x65, 0x64, 0x62, 0x61, - 0x63, 0x6b, 0x12, 0x21, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x42, 0x61, - 0x64, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x46, 0x65, 0x65, 0x64, 0x62, 0x61, 0x63, 0x6b, 0x52, 0x65, + 0x75, 0x65, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x51, 0x0a, 0x16, 0x43, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x65, 0x55, 0x73, 0x65, 0x72, 0x41, 0x70, 0x70, 0x6c, 0x65, + 0x4d, 0x61, 0x69, 0x6c, 0x12, 0x1f, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6f, 0x6e, 0x66, + 0x69, 0x67, 0x75, 0x72, 0x65, 0x41, 0x70, 0x70, 0x6c, 0x65, 0x4d, 0x61, 0x69, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x42, 0x0a, - 0x0a, 0x4c, 0x6f, 0x67, 0x6f, 0x75, 0x74, 0x55, 0x73, 0x65, 0x72, 0x12, 0x1c, 0x2e, 0x67, 0x6f, - 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, - 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, + 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x3f, 0x0a, + 0x0e, 0x52, 0x75, 0x6e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, + 0x18, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x72, 0x65, + 0x61, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x11, 0x2e, 0x67, 0x72, 0x70, 0x63, + 0x2e, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x30, 0x01, 0x12, 0x41, + 0x0a, 0x0f, 0x53, 0x74, 0x6f, 0x70, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, + 0x6d, 0x12, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, - 0x79, 0x12, 0x42, 0x0a, 0x0a, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x55, 0x73, 0x65, 0x72, 0x12, - 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, - 0x66, 0x2e, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x16, 0x2e, - 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, - 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x51, 0x0a, 0x16, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, - 0x72, 0x65, 0x55, 0x73, 0x65, 0x72, 0x41, 0x70, 0x70, 0x6c, 0x65, 0x4d, 0x61, 0x69, 0x6c, 0x12, - 0x1f, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x65, - 0x41, 0x70, 0x70, 0x6c, 0x65, 0x4d, 0x61, 0x69, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, - 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x3f, 0x0a, 0x0e, 0x52, 0x75, 0x6e, 0x45, - 0x76, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x18, 0x2e, 0x67, 0x72, 0x70, - 0x63, 0x2e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x11, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x53, 0x74, 0x72, 0x65, - 0x61, 0x6d, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x30, 0x01, 0x12, 0x41, 0x0a, 0x0f, 0x53, 0x74, 0x6f, - 0x70, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x16, 0x2e, 0x67, - 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, - 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x42, 0x36, 0x5a, 0x34, - 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x50, 0x72, 0x6f, 0x74, 0x6f, - 0x6e, 0x4d, 0x61, 0x69, 0x6c, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x6e, 0x2d, 0x62, 0x72, 0x69, - 0x64, 0x67, 0x65, 0x2f, 0x76, 0x33, 0x2f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, - 0x67, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x79, 0x42, 0x36, 0x5a, 0x34, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, + 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x6e, 0x4d, 0x61, 0x69, 0x6c, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x6e, 0x2d, 0x62, 0x72, 0x69, 0x64, 0x67, 0x65, 0x2f, 0x76, 0x33, 0x2f, 0x69, 0x6e, 0x74, 0x65, + 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x67, 0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x33, } var ( @@ -4679,7 +5063,7 @@ func file_bridge_proto_rawDescGZIP() []byte { } var file_bridge_proto_enumTypes = make([]protoimpl.EnumInfo, 7) -var file_bridge_proto_msgTypes = make([]protoimpl.MessageInfo, 59) +var file_bridge_proto_msgTypes = make([]protoimpl.MessageInfo, 64) var file_bridge_proto_goTypes = []interface{}{ (LogLevel)(0), // 0: grpc.LogLevel (UserState)(0), // 1: grpc.UserState @@ -4746,11 +5130,16 @@ var file_bridge_proto_goTypes = []interface{}{ (*UserDisconnectedEvent)(nil), // 62: grpc.UserDisconnectedEvent (*UserChangedEvent)(nil), // 63: grpc.UserChangedEvent (*UserBadEvent)(nil), // 64: grpc.UserBadEvent - (*GenericErrorEvent)(nil), // 65: grpc.GenericErrorEvent - (*wrapperspb.StringValue)(nil), // 66: google.protobuf.StringValue - (*emptypb.Empty)(nil), // 67: google.protobuf.Empty - (*wrapperspb.BoolValue)(nil), // 68: google.protobuf.BoolValue - (*wrapperspb.Int32Value)(nil), // 69: google.protobuf.Int32Value + (*UsedBytesChangedEvent)(nil), // 65: grpc.UsedBytesChangedEvent + (*ImapLoginFailedEvent)(nil), // 66: grpc.ImapLoginFailedEvent + (*SyncStartedEvent)(nil), // 67: grpc.SyncStartedEvent + (*SyncFinishedEvent)(nil), // 68: grpc.SyncFinishedEvent + (*SyncProgressEvent)(nil), // 69: grpc.SyncProgressEvent + (*GenericErrorEvent)(nil), // 70: grpc.GenericErrorEvent + (*wrapperspb.StringValue)(nil), // 71: google.protobuf.StringValue + (*emptypb.Empty)(nil), // 72: google.protobuf.Empty + (*wrapperspb.BoolValue)(nil), // 73: google.protobuf.BoolValue + (*wrapperspb.Int32Value)(nil), // 74: google.protobuf.Int32Value } var file_bridge_proto_depIdxs = []int32{ 0, // 0: grpc.AddLogEntryRequest.level:type_name -> grpc.LogLevel @@ -4764,7 +5153,7 @@ var file_bridge_proto_depIdxs = []int32{ 51, // 8: grpc.StreamEvent.keychain:type_name -> grpc.KeychainEvent 55, // 9: grpc.StreamEvent.mail:type_name -> grpc.MailEvent 60, // 10: grpc.StreamEvent.user:type_name -> grpc.UserEvent - 65, // 11: grpc.StreamEvent.genericError:type_name -> grpc.GenericErrorEvent + 70, // 11: grpc.StreamEvent.genericError:type_name -> grpc.GenericErrorEvent 22, // 12: grpc.AppEvent.internetStatus:type_name -> grpc.InternetStatusEvent 23, // 13: grpc.AppEvent.toggleAutostartFinished:type_name -> grpc.ToggleAutostartFinishedEvent 24, // 14: grpc.AppEvent.resetFinished:type_name -> grpc.ResetFinishedEvent @@ -4807,122 +5196,127 @@ var file_bridge_proto_depIdxs = []int32{ 62, // 51: grpc.UserEvent.userDisconnected:type_name -> grpc.UserDisconnectedEvent 63, // 52: grpc.UserEvent.userChanged:type_name -> grpc.UserChangedEvent 64, // 53: grpc.UserEvent.userBadEvent:type_name -> grpc.UserBadEvent - 6, // 54: grpc.GenericErrorEvent.code:type_name -> grpc.ErrorCode - 66, // 55: grpc.Bridge.CheckTokens:input_type -> google.protobuf.StringValue - 7, // 56: grpc.Bridge.AddLogEntry:input_type -> grpc.AddLogEntryRequest - 67, // 57: grpc.Bridge.GuiReady:input_type -> google.protobuf.Empty - 67, // 58: grpc.Bridge.Quit:input_type -> google.protobuf.Empty - 67, // 59: grpc.Bridge.Restart:input_type -> google.protobuf.Empty - 67, // 60: grpc.Bridge.ShowOnStartup:input_type -> google.protobuf.Empty - 68, // 61: grpc.Bridge.SetIsAutostartOn:input_type -> google.protobuf.BoolValue - 67, // 62: grpc.Bridge.IsAutostartOn:input_type -> google.protobuf.Empty - 68, // 63: grpc.Bridge.SetIsBetaEnabled:input_type -> google.protobuf.BoolValue - 67, // 64: grpc.Bridge.IsBetaEnabled:input_type -> google.protobuf.Empty - 68, // 65: grpc.Bridge.SetIsAllMailVisible:input_type -> google.protobuf.BoolValue - 67, // 66: grpc.Bridge.IsAllMailVisible:input_type -> google.protobuf.Empty - 67, // 67: grpc.Bridge.GoOs:input_type -> google.protobuf.Empty - 67, // 68: grpc.Bridge.TriggerReset:input_type -> google.protobuf.Empty - 67, // 69: grpc.Bridge.Version:input_type -> google.protobuf.Empty - 67, // 70: grpc.Bridge.LogsPath:input_type -> google.protobuf.Empty - 67, // 71: grpc.Bridge.LicensePath:input_type -> google.protobuf.Empty - 67, // 72: grpc.Bridge.ReleaseNotesPageLink:input_type -> google.protobuf.Empty - 67, // 73: grpc.Bridge.DependencyLicensesLink:input_type -> google.protobuf.Empty - 67, // 74: grpc.Bridge.LandingPageLink:input_type -> google.protobuf.Empty - 66, // 75: grpc.Bridge.SetColorSchemeName:input_type -> google.protobuf.StringValue - 67, // 76: grpc.Bridge.ColorSchemeName:input_type -> google.protobuf.Empty - 67, // 77: grpc.Bridge.CurrentEmailClient:input_type -> google.protobuf.Empty - 9, // 78: grpc.Bridge.ReportBug:input_type -> grpc.ReportBugRequest - 66, // 79: grpc.Bridge.ExportTLSCertificates:input_type -> google.protobuf.StringValue - 66, // 80: grpc.Bridge.ForceLauncher:input_type -> google.protobuf.StringValue - 66, // 81: grpc.Bridge.SetMainExecutable:input_type -> google.protobuf.StringValue - 10, // 82: grpc.Bridge.Login:input_type -> grpc.LoginRequest - 10, // 83: grpc.Bridge.Login2FA:input_type -> grpc.LoginRequest - 10, // 84: grpc.Bridge.Login2Passwords:input_type -> grpc.LoginRequest - 11, // 85: grpc.Bridge.LoginAbort:input_type -> grpc.LoginAbortRequest - 67, // 86: grpc.Bridge.CheckUpdate:input_type -> google.protobuf.Empty - 67, // 87: grpc.Bridge.InstallUpdate:input_type -> google.protobuf.Empty - 68, // 88: grpc.Bridge.SetIsAutomaticUpdateOn:input_type -> google.protobuf.BoolValue - 67, // 89: grpc.Bridge.IsAutomaticUpdateOn:input_type -> google.protobuf.Empty - 67, // 90: grpc.Bridge.DiskCachePath:input_type -> google.protobuf.Empty - 66, // 91: grpc.Bridge.SetDiskCachePath:input_type -> google.protobuf.StringValue - 68, // 92: grpc.Bridge.SetIsDoHEnabled:input_type -> google.protobuf.BoolValue - 67, // 93: grpc.Bridge.IsDoHEnabled:input_type -> google.protobuf.Empty - 67, // 94: grpc.Bridge.MailServerSettings:input_type -> google.protobuf.Empty - 12, // 95: grpc.Bridge.SetMailServerSettings:input_type -> grpc.ImapSmtpSettings - 67, // 96: grpc.Bridge.Hostname:input_type -> google.protobuf.Empty - 69, // 97: grpc.Bridge.IsPortFree:input_type -> google.protobuf.Int32Value - 67, // 98: grpc.Bridge.AvailableKeychains:input_type -> google.protobuf.Empty - 66, // 99: grpc.Bridge.SetCurrentKeychain:input_type -> google.protobuf.StringValue - 67, // 100: grpc.Bridge.CurrentKeychain:input_type -> google.protobuf.Empty - 67, // 101: grpc.Bridge.GetUserList:input_type -> google.protobuf.Empty - 66, // 102: grpc.Bridge.GetUser:input_type -> google.protobuf.StringValue - 15, // 103: grpc.Bridge.SetUserSplitMode:input_type -> grpc.UserSplitModeRequest - 16, // 104: grpc.Bridge.SendBadEventUserFeedback:input_type -> grpc.UserBadEventFeedbackRequest - 66, // 105: grpc.Bridge.LogoutUser:input_type -> google.protobuf.StringValue - 66, // 106: grpc.Bridge.RemoveUser:input_type -> google.protobuf.StringValue - 18, // 107: grpc.Bridge.ConfigureUserAppleMail:input_type -> grpc.ConfigureAppleMailRequest - 19, // 108: grpc.Bridge.RunEventStream:input_type -> grpc.EventStreamRequest - 67, // 109: grpc.Bridge.StopEventStream:input_type -> google.protobuf.Empty - 66, // 110: grpc.Bridge.CheckTokens:output_type -> google.protobuf.StringValue - 67, // 111: grpc.Bridge.AddLogEntry:output_type -> google.protobuf.Empty - 8, // 112: grpc.Bridge.GuiReady:output_type -> grpc.GuiReadyResponse - 67, // 113: grpc.Bridge.Quit:output_type -> google.protobuf.Empty - 67, // 114: grpc.Bridge.Restart:output_type -> google.protobuf.Empty - 68, // 115: grpc.Bridge.ShowOnStartup:output_type -> google.protobuf.BoolValue - 67, // 116: grpc.Bridge.SetIsAutostartOn:output_type -> google.protobuf.Empty - 68, // 117: grpc.Bridge.IsAutostartOn:output_type -> google.protobuf.BoolValue - 67, // 118: grpc.Bridge.SetIsBetaEnabled:output_type -> google.protobuf.Empty - 68, // 119: grpc.Bridge.IsBetaEnabled:output_type -> google.protobuf.BoolValue - 67, // 120: grpc.Bridge.SetIsAllMailVisible:output_type -> google.protobuf.Empty - 68, // 121: grpc.Bridge.IsAllMailVisible:output_type -> google.protobuf.BoolValue - 66, // 122: grpc.Bridge.GoOs:output_type -> google.protobuf.StringValue - 67, // 123: grpc.Bridge.TriggerReset:output_type -> google.protobuf.Empty - 66, // 124: grpc.Bridge.Version:output_type -> google.protobuf.StringValue - 66, // 125: grpc.Bridge.LogsPath:output_type -> google.protobuf.StringValue - 66, // 126: grpc.Bridge.LicensePath:output_type -> google.protobuf.StringValue - 66, // 127: grpc.Bridge.ReleaseNotesPageLink:output_type -> google.protobuf.StringValue - 66, // 128: grpc.Bridge.DependencyLicensesLink:output_type -> google.protobuf.StringValue - 66, // 129: grpc.Bridge.LandingPageLink:output_type -> google.protobuf.StringValue - 67, // 130: grpc.Bridge.SetColorSchemeName:output_type -> google.protobuf.Empty - 66, // 131: grpc.Bridge.ColorSchemeName:output_type -> google.protobuf.StringValue - 66, // 132: grpc.Bridge.CurrentEmailClient:output_type -> google.protobuf.StringValue - 67, // 133: grpc.Bridge.ReportBug:output_type -> google.protobuf.Empty - 67, // 134: grpc.Bridge.ExportTLSCertificates:output_type -> google.protobuf.Empty - 67, // 135: grpc.Bridge.ForceLauncher:output_type -> google.protobuf.Empty - 67, // 136: grpc.Bridge.SetMainExecutable:output_type -> google.protobuf.Empty - 67, // 137: grpc.Bridge.Login:output_type -> google.protobuf.Empty - 67, // 138: grpc.Bridge.Login2FA:output_type -> google.protobuf.Empty - 67, // 139: grpc.Bridge.Login2Passwords:output_type -> google.protobuf.Empty - 67, // 140: grpc.Bridge.LoginAbort:output_type -> google.protobuf.Empty - 67, // 141: grpc.Bridge.CheckUpdate:output_type -> google.protobuf.Empty - 67, // 142: grpc.Bridge.InstallUpdate:output_type -> google.protobuf.Empty - 67, // 143: grpc.Bridge.SetIsAutomaticUpdateOn:output_type -> google.protobuf.Empty - 68, // 144: grpc.Bridge.IsAutomaticUpdateOn:output_type -> google.protobuf.BoolValue - 66, // 145: grpc.Bridge.DiskCachePath:output_type -> google.protobuf.StringValue - 67, // 146: grpc.Bridge.SetDiskCachePath:output_type -> google.protobuf.Empty - 67, // 147: grpc.Bridge.SetIsDoHEnabled:output_type -> google.protobuf.Empty - 68, // 148: grpc.Bridge.IsDoHEnabled:output_type -> google.protobuf.BoolValue - 12, // 149: grpc.Bridge.MailServerSettings:output_type -> grpc.ImapSmtpSettings - 67, // 150: grpc.Bridge.SetMailServerSettings:output_type -> google.protobuf.Empty - 66, // 151: grpc.Bridge.Hostname:output_type -> google.protobuf.StringValue - 68, // 152: grpc.Bridge.IsPortFree:output_type -> google.protobuf.BoolValue - 13, // 153: grpc.Bridge.AvailableKeychains:output_type -> grpc.AvailableKeychainsResponse - 67, // 154: grpc.Bridge.SetCurrentKeychain:output_type -> google.protobuf.Empty - 66, // 155: grpc.Bridge.CurrentKeychain:output_type -> google.protobuf.StringValue - 17, // 156: grpc.Bridge.GetUserList:output_type -> grpc.UserListResponse - 14, // 157: grpc.Bridge.GetUser:output_type -> grpc.User - 67, // 158: grpc.Bridge.SetUserSplitMode:output_type -> google.protobuf.Empty - 67, // 159: grpc.Bridge.SendBadEventUserFeedback:output_type -> google.protobuf.Empty - 67, // 160: grpc.Bridge.LogoutUser:output_type -> google.protobuf.Empty - 67, // 161: grpc.Bridge.RemoveUser:output_type -> google.protobuf.Empty - 67, // 162: grpc.Bridge.ConfigureUserAppleMail:output_type -> google.protobuf.Empty - 20, // 163: grpc.Bridge.RunEventStream:output_type -> grpc.StreamEvent - 67, // 164: grpc.Bridge.StopEventStream:output_type -> google.protobuf.Empty - 110, // [110:165] is the sub-list for method output_type - 55, // [55:110] is the sub-list for method input_type - 55, // [55:55] is the sub-list for extension type_name - 55, // [55:55] is the sub-list for extension extendee - 0, // [0:55] is the sub-list for field type_name + 65, // 54: grpc.UserEvent.usedBytesChangedEvent:type_name -> grpc.UsedBytesChangedEvent + 66, // 55: grpc.UserEvent.imapLoginFailedEvent:type_name -> grpc.ImapLoginFailedEvent + 67, // 56: grpc.UserEvent.syncStartedEvent:type_name -> grpc.SyncStartedEvent + 68, // 57: grpc.UserEvent.syncFinishedEvent:type_name -> grpc.SyncFinishedEvent + 69, // 58: grpc.UserEvent.syncProgressEvent:type_name -> grpc.SyncProgressEvent + 6, // 59: grpc.GenericErrorEvent.code:type_name -> grpc.ErrorCode + 71, // 60: grpc.Bridge.CheckTokens:input_type -> google.protobuf.StringValue + 7, // 61: grpc.Bridge.AddLogEntry:input_type -> grpc.AddLogEntryRequest + 72, // 62: grpc.Bridge.GuiReady:input_type -> google.protobuf.Empty + 72, // 63: grpc.Bridge.Quit:input_type -> google.protobuf.Empty + 72, // 64: grpc.Bridge.Restart:input_type -> google.protobuf.Empty + 72, // 65: grpc.Bridge.ShowOnStartup:input_type -> google.protobuf.Empty + 73, // 66: grpc.Bridge.SetIsAutostartOn:input_type -> google.protobuf.BoolValue + 72, // 67: grpc.Bridge.IsAutostartOn:input_type -> google.protobuf.Empty + 73, // 68: grpc.Bridge.SetIsBetaEnabled:input_type -> google.protobuf.BoolValue + 72, // 69: grpc.Bridge.IsBetaEnabled:input_type -> google.protobuf.Empty + 73, // 70: grpc.Bridge.SetIsAllMailVisible:input_type -> google.protobuf.BoolValue + 72, // 71: grpc.Bridge.IsAllMailVisible:input_type -> google.protobuf.Empty + 72, // 72: grpc.Bridge.GoOs:input_type -> google.protobuf.Empty + 72, // 73: grpc.Bridge.TriggerReset:input_type -> google.protobuf.Empty + 72, // 74: grpc.Bridge.Version:input_type -> google.protobuf.Empty + 72, // 75: grpc.Bridge.LogsPath:input_type -> google.protobuf.Empty + 72, // 76: grpc.Bridge.LicensePath:input_type -> google.protobuf.Empty + 72, // 77: grpc.Bridge.ReleaseNotesPageLink:input_type -> google.protobuf.Empty + 72, // 78: grpc.Bridge.DependencyLicensesLink:input_type -> google.protobuf.Empty + 72, // 79: grpc.Bridge.LandingPageLink:input_type -> google.protobuf.Empty + 71, // 80: grpc.Bridge.SetColorSchemeName:input_type -> google.protobuf.StringValue + 72, // 81: grpc.Bridge.ColorSchemeName:input_type -> google.protobuf.Empty + 72, // 82: grpc.Bridge.CurrentEmailClient:input_type -> google.protobuf.Empty + 9, // 83: grpc.Bridge.ReportBug:input_type -> grpc.ReportBugRequest + 71, // 84: grpc.Bridge.ExportTLSCertificates:input_type -> google.protobuf.StringValue + 71, // 85: grpc.Bridge.ForceLauncher:input_type -> google.protobuf.StringValue + 71, // 86: grpc.Bridge.SetMainExecutable:input_type -> google.protobuf.StringValue + 10, // 87: grpc.Bridge.Login:input_type -> grpc.LoginRequest + 10, // 88: grpc.Bridge.Login2FA:input_type -> grpc.LoginRequest + 10, // 89: grpc.Bridge.Login2Passwords:input_type -> grpc.LoginRequest + 11, // 90: grpc.Bridge.LoginAbort:input_type -> grpc.LoginAbortRequest + 72, // 91: grpc.Bridge.CheckUpdate:input_type -> google.protobuf.Empty + 72, // 92: grpc.Bridge.InstallUpdate:input_type -> google.protobuf.Empty + 73, // 93: grpc.Bridge.SetIsAutomaticUpdateOn:input_type -> google.protobuf.BoolValue + 72, // 94: grpc.Bridge.IsAutomaticUpdateOn:input_type -> google.protobuf.Empty + 72, // 95: grpc.Bridge.DiskCachePath:input_type -> google.protobuf.Empty + 71, // 96: grpc.Bridge.SetDiskCachePath:input_type -> google.protobuf.StringValue + 73, // 97: grpc.Bridge.SetIsDoHEnabled:input_type -> google.protobuf.BoolValue + 72, // 98: grpc.Bridge.IsDoHEnabled:input_type -> google.protobuf.Empty + 72, // 99: grpc.Bridge.MailServerSettings:input_type -> google.protobuf.Empty + 12, // 100: grpc.Bridge.SetMailServerSettings:input_type -> grpc.ImapSmtpSettings + 72, // 101: grpc.Bridge.Hostname:input_type -> google.protobuf.Empty + 74, // 102: grpc.Bridge.IsPortFree:input_type -> google.protobuf.Int32Value + 72, // 103: grpc.Bridge.AvailableKeychains:input_type -> google.protobuf.Empty + 71, // 104: grpc.Bridge.SetCurrentKeychain:input_type -> google.protobuf.StringValue + 72, // 105: grpc.Bridge.CurrentKeychain:input_type -> google.protobuf.Empty + 72, // 106: grpc.Bridge.GetUserList:input_type -> google.protobuf.Empty + 71, // 107: grpc.Bridge.GetUser:input_type -> google.protobuf.StringValue + 15, // 108: grpc.Bridge.SetUserSplitMode:input_type -> grpc.UserSplitModeRequest + 16, // 109: grpc.Bridge.SendBadEventUserFeedback:input_type -> grpc.UserBadEventFeedbackRequest + 71, // 110: grpc.Bridge.LogoutUser:input_type -> google.protobuf.StringValue + 71, // 111: grpc.Bridge.RemoveUser:input_type -> google.protobuf.StringValue + 18, // 112: grpc.Bridge.ConfigureUserAppleMail:input_type -> grpc.ConfigureAppleMailRequest + 19, // 113: grpc.Bridge.RunEventStream:input_type -> grpc.EventStreamRequest + 72, // 114: grpc.Bridge.StopEventStream:input_type -> google.protobuf.Empty + 71, // 115: grpc.Bridge.CheckTokens:output_type -> google.protobuf.StringValue + 72, // 116: grpc.Bridge.AddLogEntry:output_type -> google.protobuf.Empty + 8, // 117: grpc.Bridge.GuiReady:output_type -> grpc.GuiReadyResponse + 72, // 118: grpc.Bridge.Quit:output_type -> google.protobuf.Empty + 72, // 119: grpc.Bridge.Restart:output_type -> google.protobuf.Empty + 73, // 120: grpc.Bridge.ShowOnStartup:output_type -> google.protobuf.BoolValue + 72, // 121: grpc.Bridge.SetIsAutostartOn:output_type -> google.protobuf.Empty + 73, // 122: grpc.Bridge.IsAutostartOn:output_type -> google.protobuf.BoolValue + 72, // 123: grpc.Bridge.SetIsBetaEnabled:output_type -> google.protobuf.Empty + 73, // 124: grpc.Bridge.IsBetaEnabled:output_type -> google.protobuf.BoolValue + 72, // 125: grpc.Bridge.SetIsAllMailVisible:output_type -> google.protobuf.Empty + 73, // 126: grpc.Bridge.IsAllMailVisible:output_type -> google.protobuf.BoolValue + 71, // 127: grpc.Bridge.GoOs:output_type -> google.protobuf.StringValue + 72, // 128: grpc.Bridge.TriggerReset:output_type -> google.protobuf.Empty + 71, // 129: grpc.Bridge.Version:output_type -> google.protobuf.StringValue + 71, // 130: grpc.Bridge.LogsPath:output_type -> google.protobuf.StringValue + 71, // 131: grpc.Bridge.LicensePath:output_type -> google.protobuf.StringValue + 71, // 132: grpc.Bridge.ReleaseNotesPageLink:output_type -> google.protobuf.StringValue + 71, // 133: grpc.Bridge.DependencyLicensesLink:output_type -> google.protobuf.StringValue + 71, // 134: grpc.Bridge.LandingPageLink:output_type -> google.protobuf.StringValue + 72, // 135: grpc.Bridge.SetColorSchemeName:output_type -> google.protobuf.Empty + 71, // 136: grpc.Bridge.ColorSchemeName:output_type -> google.protobuf.StringValue + 71, // 137: grpc.Bridge.CurrentEmailClient:output_type -> google.protobuf.StringValue + 72, // 138: grpc.Bridge.ReportBug:output_type -> google.protobuf.Empty + 72, // 139: grpc.Bridge.ExportTLSCertificates:output_type -> google.protobuf.Empty + 72, // 140: grpc.Bridge.ForceLauncher:output_type -> google.protobuf.Empty + 72, // 141: grpc.Bridge.SetMainExecutable:output_type -> google.protobuf.Empty + 72, // 142: grpc.Bridge.Login:output_type -> google.protobuf.Empty + 72, // 143: grpc.Bridge.Login2FA:output_type -> google.protobuf.Empty + 72, // 144: grpc.Bridge.Login2Passwords:output_type -> google.protobuf.Empty + 72, // 145: grpc.Bridge.LoginAbort:output_type -> google.protobuf.Empty + 72, // 146: grpc.Bridge.CheckUpdate:output_type -> google.protobuf.Empty + 72, // 147: grpc.Bridge.InstallUpdate:output_type -> google.protobuf.Empty + 72, // 148: grpc.Bridge.SetIsAutomaticUpdateOn:output_type -> google.protobuf.Empty + 73, // 149: grpc.Bridge.IsAutomaticUpdateOn:output_type -> google.protobuf.BoolValue + 71, // 150: grpc.Bridge.DiskCachePath:output_type -> google.protobuf.StringValue + 72, // 151: grpc.Bridge.SetDiskCachePath:output_type -> google.protobuf.Empty + 72, // 152: grpc.Bridge.SetIsDoHEnabled:output_type -> google.protobuf.Empty + 73, // 153: grpc.Bridge.IsDoHEnabled:output_type -> google.protobuf.BoolValue + 12, // 154: grpc.Bridge.MailServerSettings:output_type -> grpc.ImapSmtpSettings + 72, // 155: grpc.Bridge.SetMailServerSettings:output_type -> google.protobuf.Empty + 71, // 156: grpc.Bridge.Hostname:output_type -> google.protobuf.StringValue + 73, // 157: grpc.Bridge.IsPortFree:output_type -> google.protobuf.BoolValue + 13, // 158: grpc.Bridge.AvailableKeychains:output_type -> grpc.AvailableKeychainsResponse + 72, // 159: grpc.Bridge.SetCurrentKeychain:output_type -> google.protobuf.Empty + 71, // 160: grpc.Bridge.CurrentKeychain:output_type -> google.protobuf.StringValue + 17, // 161: grpc.Bridge.GetUserList:output_type -> grpc.UserListResponse + 14, // 162: grpc.Bridge.GetUser:output_type -> grpc.User + 72, // 163: grpc.Bridge.SetUserSplitMode:output_type -> google.protobuf.Empty + 72, // 164: grpc.Bridge.SendBadEventUserFeedback:output_type -> google.protobuf.Empty + 72, // 165: grpc.Bridge.LogoutUser:output_type -> google.protobuf.Empty + 72, // 166: grpc.Bridge.RemoveUser:output_type -> google.protobuf.Empty + 72, // 167: grpc.Bridge.ConfigureUserAppleMail:output_type -> google.protobuf.Empty + 20, // 168: grpc.Bridge.RunEventStream:output_type -> grpc.StreamEvent + 72, // 169: grpc.Bridge.StopEventStream:output_type -> google.protobuf.Empty + 115, // [115:170] is the sub-list for method output_type + 60, // [60:115] is the sub-list for method input_type + 60, // [60:60] is the sub-list for extension type_name + 60, // [60:60] is the sub-list for extension extendee + 0, // [0:60] is the sub-list for field type_name } func init() { file_bridge_proto_init() } @@ -5628,6 +6022,66 @@ func file_bridge_proto_init() { } } file_bridge_proto_msgTypes[58].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*UsedBytesChangedEvent); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_bridge_proto_msgTypes[59].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ImapLoginFailedEvent); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_bridge_proto_msgTypes[60].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SyncStartedEvent); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_bridge_proto_msgTypes[61].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SyncFinishedEvent); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_bridge_proto_msgTypes[62].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*SyncProgressEvent); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_bridge_proto_msgTypes[63].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*GenericErrorEvent); i { case 0: return &v.state @@ -5703,6 +6157,11 @@ func file_bridge_proto_init() { (*UserEvent_UserDisconnected)(nil), (*UserEvent_UserChanged)(nil), (*UserEvent_UserBadEvent)(nil), + (*UserEvent_UsedBytesChangedEvent)(nil), + (*UserEvent_ImapLoginFailedEvent)(nil), + (*UserEvent_SyncStartedEvent)(nil), + (*UserEvent_SyncFinishedEvent)(nil), + (*UserEvent_SyncProgressEvent)(nil), } type x struct{} out := protoimpl.TypeBuilder{ @@ -5710,7 +6169,7 @@ func file_bridge_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_bridge_proto_rawDesc, NumEnums: 7, - NumMessages: 59, + NumMessages: 64, NumExtensions: 0, NumServices: 1, }, diff --git a/internal/frontend/grpc/bridge.proto b/internal/frontend/grpc/bridge.proto index 1e92e2a6..e0a9ae9c 100644 --- a/internal/frontend/grpc/bridge.proto +++ b/internal/frontend/grpc/bridge.proto @@ -455,6 +455,11 @@ message UserEvent { UserDisconnectedEvent userDisconnected = 2; UserChangedEvent userChanged = 3; UserBadEvent userBadEvent = 4; + UsedBytesChangedEvent usedBytesChangedEvent = 5; + ImapLoginFailedEvent imapLoginFailedEvent = 6; + SyncStartedEvent syncStartedEvent = 7; + SyncFinishedEvent syncFinishedEvent = 8; + SyncProgressEvent syncProgressEvent = 9; } } @@ -475,6 +480,30 @@ message UserBadEvent { string errorMessage = 2; } +message UsedBytesChangedEvent { + string userID = 1; + int64 usedBytes = 2; +} + +message ImapLoginFailedEvent { + string username = 1; +} + +message SyncStartedEvent { + string userID = 1; +} + +message SyncFinishedEvent { + string userID = 1; +} + +message SyncProgressEvent { + string userID = 1; + double progress = 2; + int64 elapsedMs = 3; + int64 remainingMs = 4; +} + //********************************************************** // Generic errors //********************************************************** diff --git a/internal/frontend/grpc/event_factory.go b/internal/frontend/grpc/event_factory.go index 29b0f11c..67d87fb4 100644 --- a/internal/frontend/grpc/event_factory.go +++ b/internal/frontend/grpc/event_factory.go @@ -177,6 +177,31 @@ func NewUserBadEvent(userID string, errorMessage string) *StreamEvent { return userEvent(&UserEvent{Event: &UserEvent_UserBadEvent{UserBadEvent: &UserBadEvent{UserID: userID, ErrorMessage: errorMessage}}}) } +func NewUsedBytesChangedEvent(userID string, usedBytes int) *StreamEvent { + return userEvent(&UserEvent{Event: &UserEvent_UsedBytesChangedEvent{UsedBytesChangedEvent: &UsedBytesChangedEvent{UserID: userID, UsedBytes: int64(usedBytes)}}}) +} + +func newIMAPLoginFailedEvent(username string) *StreamEvent { + return userEvent(&UserEvent{Event: &UserEvent_ImapLoginFailedEvent{ImapLoginFailedEvent: &ImapLoginFailedEvent{Username: username}}}) +} + +func NewSyncStartedEvent(userID string) *StreamEvent { + return userEvent(&UserEvent{Event: &UserEvent_SyncStartedEvent{SyncStartedEvent: &SyncStartedEvent{UserID: userID}}}) +} + +func NewSyncFinishedEvent(userID string) *StreamEvent { + return userEvent(&UserEvent{Event: &UserEvent_SyncFinishedEvent{SyncFinishedEvent: &SyncFinishedEvent{UserID: userID}}}) +} + +func NewSyncProgressEvent(userID string, progress float64, elapsedMs, remainingMs int64) *StreamEvent { + return userEvent(&UserEvent{Event: &UserEvent_SyncProgressEvent{SyncProgressEvent: &SyncProgressEvent{ + UserID: userID, + Progress: progress, + ElapsedMs: elapsedMs, + RemainingMs: remainingMs, + }}}) +} + func NewGenericErrorEvent(errorCode ErrorCode) *StreamEvent { return genericErrorEvent(&GenericErrorEvent{Code: errorCode}) } diff --git a/internal/frontend/grpc/service.go b/internal/frontend/grpc/service.go index e1d281a8..89c6b585 100644 --- a/internal/frontend/grpc/service.go +++ b/internal/frontend/grpc/service.go @@ -25,6 +25,7 @@ import ( "errors" "fmt" "io/fs" + "math/rand" "net" "os" "path/filepath" @@ -37,6 +38,7 @@ import ( "github.com/ProtonMail/proton-bridge/v3/internal/certs" "github.com/ProtonMail/proton-bridge/v3/internal/events" "github.com/ProtonMail/proton-bridge/v3/internal/safe" + "github.com/ProtonMail/proton-bridge/v3/internal/service" "github.com/ProtonMail/proton-bridge/v3/internal/updater" "github.com/bradenaw/juniper/xslices" "github.com/elastic/go-sysinfo" @@ -93,12 +95,10 @@ type Service struct { // nolint:structcheck } // NewService returns a new instance of the service. -// -// nolint:funlen func NewService( panicHandler CrashHandler, restarter Restarter, - locations Locator, + locations service.Locator, bridge *bridge.Bridge, eventCh <-chan events.Event, quitCh <-chan struct{}, @@ -110,7 +110,7 @@ func NewService( logrus.WithError(err).Panic("Could not generate gRPC TLS config") } - config := Config{ + config := service.Config{ Cert: string(certPEM), Token: uuid.NewString(), } @@ -141,7 +141,7 @@ func NewService( config.Port = address.Port } - if path, err := saveGRPCServerConfigFile(locations, &config); err != nil { + if path, err := service.SaveGRPCServerConfigFile(locations, &config, serverConfigFileName); err != nil { logrus.WithError(err).WithField("path", path).Panic("Could not write gRPC service config file") } else { logrus.WithField("path", path).Info("Successfully saved gRPC service config file") @@ -245,7 +245,7 @@ func (s *Service) WaitUntilFrontendIsReady() { s.initializing.Wait() } -// nolint:funlen,gocyclo +// nolint:gocyclo func (s *Service) watchEvents() { // GODT-1949 Better error events. for _, err := range s.bridge.GetErrors() { @@ -255,12 +255,6 @@ func (s *Service) watchEvents() { case errors.Is(err, bridge.ErrVaultInsecure): _ = s.SendEvent(NewKeychainHasNoKeychainEvent()) - - case errors.Is(err, bridge.ErrServeIMAP): - _ = s.SendEvent(NewMailServerSettingsErrorEvent(MailServerSettingsErrorType_IMAP_PORT_STARTUP_ERROR)) - - case errors.Is(err, bridge.ErrServeSMTP): - _ = s.SendEvent(NewMailServerSettingsErrorEvent(MailServerSettingsErrorType_SMTP_PORT_STARTUP_ERROR)) } } @@ -272,6 +266,12 @@ func (s *Service) watchEvents() { case events.ConnStatusDown: _ = s.SendEvent(NewInternetStatusEvent(false)) + case events.IMAPServerError: + _ = s.SendEvent(NewMailServerSettingsErrorEvent(MailServerSettingsErrorType_IMAP_PORT_STARTUP_ERROR)) + + case events.SMTPServerError: + _ = s.SendEvent(NewMailServerSettingsErrorEvent(MailServerSettingsErrorType_SMTP_PORT_STARTUP_ERROR)) + case events.Raise: _ = s.SendEvent(NewShowMainWindowEvent()) @@ -305,6 +305,12 @@ func (s *Service) watchEvents() { case events.AddressModeChanged: _ = s.SendEvent(NewUserChangedEvent(event.UserID)) + case events.UsedSpaceChanged: + _ = s.SendEvent(NewUsedBytesChangedEvent(event.UserID, event.UsedSpace)) + + case events.IMAPLoginFailed: + _ = s.SendEvent(newIMAPLoginFailedEvent(event.Username)) + case events.UserDeauth: // This is the event the GUI cares about. _ = s.SendEvent(NewUserChangedEvent(event.UserID)) @@ -317,6 +323,15 @@ func (s *Service) watchEvents() { case events.UserBadEvent: _ = s.SendEvent(NewUserBadEvent(event.UserID, event.Error.Error())) + case events.SyncStarted: + _ = s.SendEvent(NewSyncStartedEvent(event.UserID)) + + case events.SyncFinished: + _ = s.SendEvent(NewSyncFinishedEvent(event.UserID)) + + case events.SyncProgress: + _ = s.SendEvent(NewSyncProgressEvent(event.UserID, event.Progress, event.Elapsed.Milliseconds(), event.Remaining.Milliseconds())) + case events.UpdateLatest: safe.RLock(func() { s.latest = event.Version @@ -481,17 +496,6 @@ func newTLSConfig() (*tls.Config, []byte, error) { }, certPEM, nil } -func saveGRPCServerConfigFile(locations Locator, config *Config) (string, error) { - settingsPath, err := locations.ProvideSettingsPath() - if err != nil { - return "", err - } - - configPath := filepath.Join(settingsPath, serverConfigFileName) - - return configPath, config.save(configPath) -} - // validateServerToken verify that the server token provided by the client is valid. func validateServerToken(ctx context.Context, wantToken string) error { values, ok := metadata.FromIncomingContext(ctx) @@ -577,10 +581,17 @@ func (s *Service) monitorParentPID() { func computeFileSocketPath() (string, error) { tempPath := os.TempDir() for i := 0; i < 1000; i++ { - path := filepath.Join(tempPath, fmt.Sprintf("bridge_%v.sock", uuid.NewString())) + path := filepath.Join(tempPath, fmt.Sprintf("bridge%04d", rand.Intn(10000))) // nolint:gosec if _, err := os.Stat(path); errors.Is(err, fs.ErrNotExist) { return path, nil } + + if err := os.Remove(path); err != nil { + logrus.WithField("path", path).WithError(err).Warning("Could not remove existing socket file") + continue + } + + return path, nil } return "", errors.New("unable to find a suitable file socket in user config folder") diff --git a/internal/frontend/grpc/service_methods.go b/internal/frontend/grpc/service_methods.go index f9b9cb31..251a2d84 100644 --- a/internal/frontend/grpc/service_methods.go +++ b/internal/frontend/grpc/service_methods.go @@ -32,6 +32,7 @@ import ( "github.com/ProtonMail/proton-bridge/v3/internal/events" "github.com/ProtonMail/proton-bridge/v3/internal/frontend/theme" "github.com/ProtonMail/proton-bridge/v3/internal/safe" + "github.com/ProtonMail/proton-bridge/v3/internal/service" "github.com/ProtonMail/proton-bridge/v3/internal/updater" "github.com/ProtonMail/proton-bridge/v3/pkg/keychain" "github.com/ProtonMail/proton-bridge/v3/pkg/ports" @@ -51,8 +52,8 @@ func (s *Service) CheckTokens(ctx context.Context, clientConfigPath *wrapperspb. path := clientConfigPath.Value logEntry := s.log.WithField("path", path) - var clientConfig Config - if err := clientConfig.load(path); err != nil { + var clientConfig service.Config + if err := clientConfig.Load(path); err != nil { logEntry.WithError(err).Error("Could not read gRPC client config file") return nil, err diff --git a/internal/frontend/grpc/service_stream.go b/internal/frontend/grpc/service_stream.go index 5a727585..c7243523 100644 --- a/internal/frontend/grpc/service_stream.go +++ b/internal/frontend/grpc/service_stream.go @@ -110,7 +110,7 @@ func (s *Service) SendEvent(event *StreamEvent) error { } // StartEventTest sends all the known event via gRPC. -func (s *Service) StartEventTest() error { //nolint:funlen +func (s *Service) StartEventTest() error { const dummyAddress = "dummy@proton.me" events := []*StreamEvent{ // app @@ -174,6 +174,7 @@ func (s *Service) StartEventTest() error { //nolint:funlen NewUserToggleSplitModeFinishedEvent("userID"), NewUserDisconnectedEvent("username"), NewUserChangedEvent("userID"), + NewUsedBytesChangedEvent("userID", 1000), } for _, event := range events { diff --git a/internal/frontend/grpc/types.go b/internal/frontend/grpc/types.go index 72aa819d..f92f8b3b 100644 --- a/internal/frontend/grpc/types.go +++ b/internal/frontend/grpc/types.go @@ -26,7 +26,3 @@ type Restarter interface { AddFlags(flags ...string) Override(exe string) } - -type Locator interface { - ProvideSettingsPath() (string, error) -} diff --git a/internal/locations/locations.go b/internal/locations/locations.go index 197549a4..8a5c14d5 100644 --- a/internal/locations/locations.go +++ b/internal/locations/locations.go @@ -217,14 +217,13 @@ func (l *Locations) getUpdatesPath() string { } // Clear removes everything except the lock and update files. -func (l *Locations) Clear() error { +func (l *Locations) Clear(except ...string) error { return files.Remove( l.userConfig, l.userData, l.userCache, ).Except( - l.GetGuiLockFile(), - l.getUpdatesPath(), + append(except, l.GetGuiLockFile(), l.getUpdatesPath())..., ).Do() } diff --git a/internal/frontend/grpc/config.go b/internal/service/config.go similarity index 79% rename from internal/frontend/grpc/config.go rename to internal/service/config.go index 03842e63..7392b1ba 100644 --- a/internal/frontend/grpc/config.go +++ b/internal/service/config.go @@ -15,11 +15,12 @@ // You should have received a copy of the GNU General Public License // along with Proton Mail Bridge. If not, see . -package grpc +package service import ( "encoding/json" "os" + "path/filepath" ) // Config is a structure containing the service configuration data that are exchanged by the gRPC server and client. @@ -53,8 +54,8 @@ func (s *Config) _save(path string) error { return json.NewEncoder(f).Encode(s) } -// load loads a gRPC service configuration from file. -func (s *Config) load(path string) error { +// Load loads a gRPC service configuration from file. +func (s *Config) Load(path string) error { f, err := os.Open(path) //nolint:errcheck,gosec if err != nil { return err @@ -64,3 +65,15 @@ func (s *Config) load(path string) error { return json.NewDecoder(f).Decode(s) } + +// SaveGRPCServerConfigFile save GRPC configuration file. +func SaveGRPCServerConfigFile(locations Locator, config *Config, filename string) (string, error) { + settingsPath, err := locations.ProvideSettingsPath() + if err != nil { + return "", err + } + + configPath := filepath.Join(settingsPath, filename) + + return configPath, config.save(configPath) +} diff --git a/internal/frontend/grpc/config_test.go b/internal/service/config_test.go similarity index 93% rename from internal/frontend/grpc/config_test.go rename to internal/service/config_test.go index 432c74ba..ecc3a4e4 100644 --- a/internal/frontend/grpc/config_test.go +++ b/internal/service/config_test.go @@ -15,7 +15,7 @@ // You should have received a copy of the GNU General Public License // along with Proton Mail Bridge. If not, see . -package grpc +package service import ( "path/filepath" @@ -46,11 +46,11 @@ func TestConfig(t *testing.T) { require.NoError(t, conf1.save(tempFilePath)) conf2 := Config{} - require.NoError(t, conf2.load(tempFilePath)) + require.NoError(t, conf2.Load(tempFilePath)) require.Equal(t, conf1, conf2) // failure to load - require.Error(t, conf2.load(tempFilePath+"_")) + require.Error(t, conf2.Load(tempFilePath+"_")) // failure to save require.Error(t, conf2.save(filepath.Join(tempDir, "non/existing/folder", tempFileName))) diff --git a/internal/service/types.go b/internal/service/types.go new file mode 100644 index 00000000..faa77624 --- /dev/null +++ b/internal/service/types.go @@ -0,0 +1,22 @@ +// Copyright (c) 2023 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 . + +package service + +type Locator interface { + ProvideSettingsPath() (string, error) +} diff --git a/internal/updater/sync.go b/internal/updater/sync.go index 440cbd5d..8137a93b 100644 --- a/internal/updater/sync.go +++ b/internal/updater/sync.go @@ -142,7 +142,7 @@ func checksum(path string) (hash string) { // srcDir including app folder. // dstDir including app folder. -func copyRecursively(srcDir, dstDir string) error { //nolint:funlen +func copyRecursively(srcDir, dstDir string) error { return filepath.Walk(srcDir, func(srcPath string, srcInfo os.FileInfo, err error) error { if err != nil { return err diff --git a/internal/user/events.go b/internal/user/events.go index d5a4fec1..72131a3b 100644 --- a/internal/user/events.go +++ b/internal/user/events.go @@ -18,6 +18,7 @@ package user import ( + "bytes" "context" "errors" "fmt" @@ -67,6 +68,10 @@ func (user *User) handleAPIEvent(ctx context.Context, event proton.Event) error } } + if event.UsedSpace != nil { + user.handleUsedSpaceChange(*event.UsedSpace) + } + return nil } @@ -194,6 +199,12 @@ func (user *User) handleCreateAddressEvent(ctx context.Context, event proton.Add user.apiAddrs[event.Address.ID] = event.Address + // If the address is disabled. + if event.Address.Status != proton.AddressStatusEnabled { + return nil + } + + // If the address is enabled, we need to hook it up to the update channels. switch user.vault.AddressMode() { case vault.CombinedMode: primAddr, err := getAddrIdx(user.apiAddrs, 0) @@ -220,6 +231,10 @@ func (user *User) handleCreateAddressEvent(ctx context.Context, event proton.Add // Perform the sync in an RLock. return safe.RLockRet(func() error { + if event.Address.Status != proton.AddressStatusEnabled { + return nil + } + if user.vault.AddressMode() == vault.SplitMode { if err := syncLabels(ctx, user.apiLabels, user.updateCh[event.Address.ID]); err != nil { return fmt.Errorf("failed to sync labels to new address: %w", err) @@ -237,18 +252,58 @@ func (user *User) handleUpdateAddressEvent(_ context.Context, event proton.Addre "email": logging.Sensitive(event.Address.Email), }).Info("Handling address updated event") - if _, ok := user.apiAddrs[event.Address.ID]; !ok { + oldAddr, ok := user.apiAddrs[event.Address.ID] + if !ok { user.log.Debugf("Address %q does not exist", event.Address.ID) return nil } user.apiAddrs[event.Address.ID] = event.Address - user.eventCh.Enqueue(events.UserAddressUpdated{ - UserID: user.apiUser.ID, - AddressID: event.Address.ID, - Email: event.Address.Email, - }) + switch { + // If the address was newly enabled: + case oldAddr.Status != proton.AddressStatusEnabled && event.Address.Status == proton.AddressStatusEnabled: + switch user.vault.AddressMode() { + case vault.CombinedMode: + primAddr, err := getAddrIdx(user.apiAddrs, 0) + if err != nil { + return fmt.Errorf("failed to get primary address: %w", err) + } + + user.updateCh[event.Address.ID] = user.updateCh[primAddr.ID] + + case vault.SplitMode: + user.updateCh[event.Address.ID] = queue.NewQueuedChannel[imap.Update](0, 0) + } + + user.eventCh.Enqueue(events.UserAddressEnabled{ + UserID: user.apiUser.ID, + AddressID: event.Address.ID, + Email: event.Address.Email, + }) + + // If the address was newly disabled: + case oldAddr.Status == proton.AddressStatusEnabled && event.Address.Status != proton.AddressStatusEnabled: + if user.vault.AddressMode() == vault.SplitMode { + user.updateCh[event.ID].CloseAndDiscardQueued() + } + + delete(user.updateCh, event.ID) + + user.eventCh.Enqueue(events.UserAddressDisabled{ + UserID: user.apiUser.ID, + AddressID: event.Address.ID, + Email: event.Address.Email, + }) + + // Otherwise it's just an update: + default: + user.eventCh.Enqueue(events.UserAddressUpdated{ + UserID: user.apiUser.ID, + AddressID: event.Address.ID, + Email: event.Address.Email, + }) + } return nil }, user.apiAddrsLock, user.updateChLock) @@ -264,12 +319,20 @@ func (user *User) handleDeleteAddressEvent(_ context.Context, event proton.Addre return nil } - if user.vault.AddressMode() == vault.SplitMode { - user.updateCh[event.ID].CloseAndDiscardQueued() - delete(user.updateCh, event.ID) + delete(user.apiAddrs, event.ID) + + // If the address was disabled to begin with, we don't need to do anything. + if addr.Status != proton.AddressStatusEnabled { + return nil } - delete(user.apiAddrs, event.ID) + // Otherwise, in split mode, drop the update queue. + if user.vault.AddressMode() == vault.SplitMode { + user.updateCh[event.ID].CloseAndDiscardQueued() + } + + // And in either mode, remove the address from the update channel map. + delete(user.updateCh, event.ID) user.eventCh.Enqueue(events.UserAddressDeleted{ UserID: user.apiUser.ID, @@ -356,25 +419,51 @@ func (user *User) handleUpdateLabelEvent(ctx context.Context, event proton.Label "name": logging.Sensitive(event.Label.Name), }).Info("Handling label updated event") - // Only update the label if it exists; we don't want to create it as a client may have just deleted it. - if _, ok := user.apiLabels[event.Label.ID]; ok { - user.apiLabels[event.Label.ID] = event.Label - } + stack := []proton.Label{event.Label} - for _, updateCh := range xslices.Unique(maps.Values(user.updateCh)) { - update := imap.NewMailboxUpdated( - imap.MailboxID(event.ID), - getMailboxName(event.Label), - ) - updateCh.Enqueue(update) - updates = append(updates, update) - } + for len(stack) > 0 { + label := stack[0] + stack = stack[1:] - user.eventCh.Enqueue(events.UserLabelUpdated{ - UserID: user.apiUser.ID, - LabelID: event.Label.ID, - Name: event.Label.Name, - }) + // Only update the label if it exists; we don't want to create it as a client may have just deleted it. + if _, ok := user.apiLabels[label.ID]; ok { + user.apiLabels[label.ID] = event.Label + } + + // API doesn't notify us that the path has changed. We need to fetch it again. + apiLabel, err := user.client.GetLabel(ctx, label.ID, label.Type) + if apiErr := new(proton.APIError); errors.As(err, &apiErr) && apiErr.Status == http.StatusUnprocessableEntity { + user.log.WithError(apiErr).Warn("Failed to get label: label does not exist") + continue + } else if err != nil { + return nil, fmt.Errorf("failed to get label %q: %w", label.ID, err) + } + + // Update the label in the map. + user.apiLabels[apiLabel.ID] = apiLabel + + // Notify the IMAP clients. + for _, updateCh := range xslices.Unique(maps.Values(user.updateCh)) { + update := imap.NewMailboxUpdated( + imap.MailboxID(apiLabel.ID), + getMailboxName(apiLabel), + ) + updateCh.Enqueue(update) + updates = append(updates, update) + } + + user.eventCh.Enqueue(events.UserLabelUpdated{ + UserID: user.apiUser.ID, + LabelID: apiLabel.ID, + Name: apiLabel.Name, + }) + + children := xslices.Filter(maps.Values(user.apiLabels), func(other proton.Label) bool { + return other.ParentID == label.ID + }) + + stack = append(stack, children...) + } return updates, nil }, user.apiLabelsLock, user.updateChLock) @@ -404,7 +493,7 @@ func (user *User) handleDeleteLabelEvent(ctx context.Context, event proton.Label } // handleMessageEvents handles the given message events. -func (user *User) handleMessageEvents(ctx context.Context, messageEvents []proton.MessageEvent) error { //nolint:funlen +func (user *User) handleMessageEvents(ctx context.Context, messageEvents []proton.MessageEvent) error { for _, event := range messageEvents { ctx = logging.WithLogrusField(ctx, "messageID", event.ID) @@ -494,7 +583,7 @@ func (user *User) handleCreateMessageEvent(ctx context.Context, message proton.M "subject": logging.Sensitive(message.Subject), }).Info("Handling message created event") - full, err := user.client.GetFullMessage(ctx, message.ID) + full, err := user.client.GetFullMessage(ctx, message.ID, newProtonAPIScheduler(), proton.NewDefaultAttachmentAllocator()) if err != nil { // If the message is not found, it means that it has been deleted before we could fetch it. if apiErr := new(proton.APIError); errors.As(err, &apiErr) && apiErr.Status == http.StatusUnprocessableEntity { @@ -509,7 +598,7 @@ func (user *User) handleCreateMessageEvent(ctx context.Context, message proton.M var update imap.Update if err := withAddrKR(user.apiUser, user.apiAddrs[message.AddressID], user.vault.KeyPass(), func(_, addrKR *crypto.KeyRing) error { - res := buildRFC822(user.apiLabels, full, addrKR) + res := buildRFC822(user.apiLabels, full, addrKR, new(bytes.Buffer)) if res.err != nil { user.log.WithError(err).Error("Failed to build RFC822 message") @@ -553,7 +642,7 @@ func (user *User) handleUpdateMessageEvent(ctx context.Context, message proton.M Seen: message.Seen(), Flagged: message.Starred(), Draft: message.IsDraft(), - Answered: message.IsReplied == true || message.IsRepliedAll == true, //nolint: gosimple + Answered: message.IsRepliedAll == true || message.IsReplied == true, //nolint: gosimple }, ) @@ -586,7 +675,7 @@ func (user *User) handleUpdateDraftEvent(ctx context.Context, event proton.Messa "subject": logging.Sensitive(event.Message.Subject), }).Info("Handling draft updated event") - full, err := user.client.GetFullMessage(ctx, event.Message.ID) + full, err := user.client.GetFullMessage(ctx, event.Message.ID, newProtonAPIScheduler(), proton.NewDefaultAttachmentAllocator()) if err != nil { // If the message is not found, it means that it has been deleted before we could fetch it. if apiErr := new(proton.APIError); errors.As(err, &apiErr) && apiErr.Status == http.StatusUnprocessableEntity { @@ -600,7 +689,7 @@ func (user *User) handleUpdateDraftEvent(ctx context.Context, event proton.Messa var update imap.Update if err := withAddrKR(user.apiUser, user.apiAddrs[event.Message.AddressID], user.vault.KeyPass(), func(_, addrKR *crypto.KeyRing) error { - res := buildRFC822(user.apiLabels, full, addrKR) + res := buildRFC822(user.apiLabels, full, addrKR, new(bytes.Buffer)) if res.err != nil { logrus.WithError(err).Error("Failed to build RFC822 message") @@ -637,6 +726,20 @@ func (user *User) handleUpdateDraftEvent(ctx context.Context, event proton.Messa }, user.apiUserLock, user.apiAddrsLock, user.apiLabelsLock, user.updateChLock) } +func (user *User) handleUsedSpaceChange(usedSpace int) { + safe.Lock(func() { + if user.apiUser.UsedSpace == usedSpace { + return + } + + user.apiUser.UsedSpace = usedSpace + user.eventCh.Enqueue(events.UsedSpaceChanged{ + UserID: user.apiUser.ID, + UsedSpace: usedSpace, + }) + }, user.apiUserLock) +} + func getMailboxName(label proton.Label) []string { var name []string diff --git a/internal/user/imap.go b/internal/user/imap.go index bcef92ea..fb5cdaa3 100644 --- a/internal/user/imap.go +++ b/internal/user/imap.go @@ -264,8 +264,6 @@ func (conn *imapConnector) DeleteMailbox(ctx context.Context, labelID imap.Mailb } // CreateMessage creates a new message on the remote. -// -// nolint:funlen func (conn *imapConnector) CreateMessage( ctx context.Context, mailboxID imap.MailboxID, @@ -292,7 +290,7 @@ func (conn *imapConnector) CreateMessage( conn.log.WithField("messageID", messageID).Warn("Message already sent") // Query the server-side message. - full, err := conn.client.GetFullMessage(ctx, messageID) + full, err := conn.client.GetFullMessage(ctx, messageID, newProtonAPIScheduler(), proton.NewDefaultAttachmentAllocator()) if err != nil { return imap.Message{}, nil, fmt.Errorf("failed to fetch message: %w", err) } @@ -356,7 +354,7 @@ func (conn *imapConnector) CreateMessage( } func (conn *imapConnector) GetMessageLiteral(ctx context.Context, id imap.MessageID) ([]byte, error) { - msg, err := conn.client.GetFullMessage(ctx, string(id)) + msg, err := conn.client.GetFullMessage(ctx, string(id), newProtonAPIScheduler(), proton.NewDefaultAttachmentAllocator()) if err != nil { return nil, err } @@ -382,7 +380,7 @@ func (conn *imapConnector) GetMessageLiteral(ctx context.Context, id imap.Messag func (conn *imapConnector) AddMessagesToMailbox(ctx context.Context, messageIDs []imap.MessageID, mailboxID imap.MailboxID) error { defer conn.goPollAPIEvents(false) - if mailboxID == proton.AllMailLabel { + if isAllMailOrScheduled(mailboxID) { return connector.ErrOperationNotAllowed } @@ -393,7 +391,7 @@ func (conn *imapConnector) AddMessagesToMailbox(ctx context.Context, messageIDs func (conn *imapConnector) RemoveMessagesFromMailbox(ctx context.Context, messageIDs []imap.MessageID, mailboxID imap.MailboxID) error { defer conn.goPollAPIEvents(false) - if mailboxID == proton.AllMailLabel { + if isAllMailOrScheduled(mailboxID) { return connector.ErrOperationNotAllowed } @@ -442,8 +440,8 @@ func (conn *imapConnector) MoveMessages(ctx context.Context, messageIDs []imap.M if (labelFromID == proton.InboxLabel && labelToID == proton.SentLabel) || (labelFromID == proton.SentLabel && labelToID == proton.InboxLabel) || - labelFromID == proton.AllMailLabel || - labelToID == proton.AllMailLabel { + isAllMailOrScheduled(labelFromID) || + isAllMailOrScheduled(labelToID) { return false, connector.ErrOperationNotAllowed } @@ -507,19 +505,20 @@ func (conn *imapConnector) GetUpdates() <-chan imap.Update { }, conn.updateChLock) } -// GetUIDValidity returns the default UID validity for this user. -func (conn *imapConnector) GetUIDValidity() imap.UID { - return conn.vault.GetUIDValidity(conn.addrID) -} +// GetMailboxVisibility returns the visibility of a mailbox over IMAP. +func (conn *imapConnector) GetMailboxVisibility(_ context.Context, mailboxID imap.MailboxID) imap.MailboxVisibility { + switch mailboxID { + case proton.AllMailLabel: + if atomic.LoadUint32(&conn.showAllMail) != 0 { + return imap.Visible + } + return imap.Hidden -// SetUIDValidity sets the default UID validity for this user. -func (conn *imapConnector) SetUIDValidity(validity imap.UID) error { - return conn.vault.SetUIDValidity(conn.addrID, validity) -} - -// IsMailboxVisible returns whether this mailbox should be visible over IMAP. -func (conn *imapConnector) IsMailboxVisible(_ context.Context, mailboxID imap.MailboxID) bool { - return atomic.LoadUint32(&conn.showAllMail) != 0 || mailboxID != proton.AllMailLabel + case proton.AllScheduledLabel: + return imap.HiddenIfEmpty + default: + return imap.Visible + } } // Close the connector will no longer be used and all resources should be closed/released. @@ -550,7 +549,7 @@ func (conn *imapConnector) importMessage( messageID = msg.ID } else { - res, err := stream.Collect(ctx, conn.client.ImportMessages(ctx, addrKR, 1, 1, []proton.ImportReq{{ + str, err := conn.client.ImportMessages(ctx, addrKR, 1, 1, []proton.ImportReq{{ Metadata: proton.ImportMetadata{ AddressID: conn.addrID, LabelIDs: labelIDs, @@ -558,7 +557,12 @@ func (conn *imapConnector) importMessage( Flags: flags, }, Message: literal, - }}...)) + }}...) + if err != nil { + return fmt.Errorf("failed to prepare message for import: %w", err) + } + + res, err := stream.Collect(ctx, str) if err != nil { return fmt.Errorf("failed to import message: %w", err) } @@ -568,7 +572,7 @@ func (conn *imapConnector) importMessage( var err error - if full, err = conn.client.GetFullMessage(ctx, messageID); err != nil { + if full, err = conn.client.GetFullMessage(ctx, messageID, newProtonAPIScheduler(), proton.NewDefaultAttachmentAllocator()); err != nil { return fmt.Errorf("failed to fetch message: %w", err) } @@ -615,7 +619,7 @@ func toIMAPMessage(message proton.MessageMetadata) imap.Message { } } -func (conn *imapConnector) createDraft(ctx context.Context, literal []byte, addrKR *crypto.KeyRing, sender proton.Address) (proton.Message, error) { //nolint:funlen +func (conn *imapConnector) createDraft(ctx context.Context, literal []byte, addrKR *crypto.KeyRing, sender proton.Address) (proton.Message, error) { // Create a new message parser from the reader. parser, err := parser.New(bytes.NewReader(literal)) if err != nil { @@ -687,3 +691,7 @@ func toIMAPMailbox(label proton.Label, flags, permFlags, attrs imap.FlagSet) ima Attributes: attrs, } } + +func isAllMailOrScheduled(mailboxID imap.MailboxID) bool { + return (mailboxID == proton.AllMailLabel) || (mailboxID == proton.AllScheduledLabel) +} diff --git a/internal/user/send_recorder.go b/internal/user/send_recorder.go index c83c9ef1..283b35eb 100644 --- a/internal/user/send_recorder.go +++ b/internal/user/send_recorder.go @@ -218,8 +218,6 @@ func (h *sendRecorder) getWaitCh(hash string) (<-chan struct{}, bool) { // - the Content-Type header of each (leaf) part, // - the Content-Disposition header of each (leaf) part, // - the (decoded) body of each part. -// -// nolint:funlen func getMessageHash(b []byte) (string, error) { section := rfc822.Parse(b) diff --git a/internal/user/smtp.go b/internal/user/smtp.go index b9f0c477..0fb6fafd 100644 --- a/internal/user/smtp.go +++ b/internal/user/smtp.go @@ -47,8 +47,6 @@ import ( ) // sendMail sends an email from the given address to the given recipients. -// -// nolint:funlen func (user *User) sendMail(authID string, from string, to []string, r io.Reader) error { return safe.RLockRet(func() error { ctx, cancel := context.WithCancel(context.Background()) @@ -165,7 +163,7 @@ func (user *User) sendMail(authID string, from string, to []string, r io.Reader) } // sendWithKey sends the message with the given address key. -func sendWithKey( //nolint:funlen +func sendWithKey( ctx context.Context, client *proton.Client, sentry reporter.Reporter, @@ -247,7 +245,7 @@ func sendWithKey( //nolint:funlen return res, nil } -func getParentID( //nolint:funlen +func getParentID( ctx context.Context, client *proton.Client, authAddrID string, @@ -375,7 +373,6 @@ func createDraft( }) } -// nolint:funlen func createAttachments( ctx context.Context, client *proton.Client, @@ -468,12 +465,12 @@ func getRecipients( prefs, err := parallel.MapContext(ctx, runtime.NumCPU(), addresses, func(ctx context.Context, recipient string) (proton.SendPreferences, error) { pubKeys, recType, err := client.GetPublicKeys(ctx, recipient) if err != nil { - return proton.SendPreferences{}, fmt.Errorf("failed to get public keys: %w (%v)", err, recipient) + return proton.SendPreferences{}, fmt.Errorf("failed to get public key for %v: %w", recipient, err) } contactSettings, err := getContactSettings(ctx, client, userKR, recipient) if err != nil { - return proton.SendPreferences{}, fmt.Errorf("failed to get contact settings: %w", err) + return proton.SendPreferences{}, fmt.Errorf("failed to get contact settings for %v: %w", recipient, err) } return buildSendPrefs(contactSettings, settings, pubKeys, draft.MIMEType, recType == proton.RecipientTypeInternal) diff --git a/internal/user/sync.go b/internal/user/sync.go index a7c25c0f..215c3b82 100644 --- a/internal/user/sync.go +++ b/internal/user/sync.go @@ -18,6 +18,7 @@ package user import ( + "bytes" "context" "fmt" "runtime" @@ -25,6 +26,7 @@ import ( "time" "github.com/ProtonMail/gluon/imap" + "github.com/ProtonMail/gluon/logging" "github.com/ProtonMail/gluon/queue" "github.com/ProtonMail/gluon/reporter" "github.com/ProtonMail/go-proton-api" @@ -34,18 +36,38 @@ import ( "github.com/ProtonMail/proton-bridge/v3/internal/vault" "github.com/bradenaw/juniper/parallel" "github.com/bradenaw/juniper/xslices" - "github.com/google/uuid" + "github.com/pbnjay/memory" "github.com/sirupsen/logrus" "golang.org/x/exp/maps" "golang.org/x/exp/slices" ) -const ( - maxUpdateSize = 1 << 27 // 128 MiB - maxBatchSize = 1 << 8 // 256 -) +// syncSystemLabels ensures that system labels are all known to gluon. +func (user *User) syncSystemLabels(ctx context.Context) error { + return safe.RLockRet(func() error { + var updates []imap.Update -// doSync begins syncing the users data. + for _, label := range xslices.Filter(maps.Values(user.apiLabels), func(label proton.Label) bool { return label.Type == proton.LabelTypeSystem }) { + if !wantLabel(label) { + continue + } + + for _, updateCh := range xslices.Unique(maps.Values(user.updateCh)) { + update := newSystemMailboxCreatedUpdate(imap.MailboxID(label.ID), label.Name) + updateCh.Enqueue(update) + updates = append(updates, update) + } + } + + if err := waitOnIMAPUpdates(ctx, updates); err != nil { + return fmt.Errorf("could not sync system labels: %w", err) + } + + return nil + }, user.apiUserLock, user.apiAddrsLock, user.apiLabelsLock, user.updateChLock) +} + +// doSync begins syncing the user's data. // It first ensures the latest event ID is known; if not, it fetches it. // It sends a SyncStarted event and then either SyncFinished or SyncFailed // depending on whether the sync was successful. @@ -89,7 +111,6 @@ func (user *User) doSync(ctx context.Context) error { return nil } -// nolint:funlen func (user *User) sync(ctx context.Context) error { return safe.RLockRet(func() error { return withAddrKRs(user.apiUser, user.apiAddrs, user.vault.KeyPass(), func(_ *crypto.KeyRing, addrKRs map[string]*crypto.KeyRing) error { @@ -143,7 +164,7 @@ func (user *User) sync(ctx context.Context) error { addrKRs, user.updateCh, user.eventCh, - user.syncWorkers, + user.maxSyncMemory, ); err != nil { return fmt.Errorf("failed to sync messages: %w", err) } @@ -166,7 +187,7 @@ func (user *User) sync(ctx context.Context) error { func syncLabels(ctx context.Context, apiLabels map[string]proton.Label, updateCh ...*queue.QueuedChannel[imap.Update]) error { var updates []imap.Update - // Create placeholder Folders/Labels mailboxes with a random ID and with the \Noselect attribute. + // Create placeholder Folders/Labels mailboxes with the \Noselect attribute. for _, prefix := range []string{folderPrefix, labelPrefix} { for _, updateCh := range updateCh { update := newPlaceHolderMailboxCreatedUpdate(prefix) @@ -212,7 +233,15 @@ func syncLabels(ctx context.Context, apiLabels map[string]proton.Label, updateCh return nil } -// nolint:funlen +const Kilobyte = uint64(1024) +const Megabyte = 1024 * Kilobyte +const Gigabyte = 1024 * Megabyte + +func toMB(v uint64) float64 { + return float64(v) / float64(Megabyte) +} + +// nolint:gocyclo func syncMessages( ctx context.Context, userID string, @@ -224,7 +253,7 @@ func syncMessages( addrKRs map[string]*crypto.KeyRing, updateCh map[string]*queue.QueuedChannel[imap.Update], eventCh *queue.QueuedChannel[events.Event], - syncWorkers int, + maxSyncMemory uint64, ) error { ctx, cancel := context.WithCancel(ctx) defer cancel() @@ -235,78 +264,296 @@ func syncMessages( logrus.WithFields(logrus.Fields{ "messages": len(messageIDs), - "workers": syncWorkers, "numCPU": runtime.NumCPU(), }).Info("Starting message sync") // Create the flushers, one per update channel. - flushers := make(map[string]*flusher, len(updateCh)) - - for addrID, updateCh := range updateCh { - flushers[addrID] = newFlusher(updateCh, maxUpdateSize) - } // Create a reporter to report sync progress updates. syncReporter := newSyncReporter(userID, eventCh, len(messageIDs), time.Second) defer syncReporter.done() - type flushUpdate struct { - messageID string - pushedUpdates []imap.Update - batchLen int + // Expected mem usage for this whole process should be the sum of MaxMessageBuildingMem and MaxDownloadRequestMem + // times x due to pipeline and all additional memory used by network requests and compression+io. + + // There's no point in using more than 128MB of download data per stage, after that we reach a point of diminishing + // returns as we can't keep the pipeline fed fast enough. + const MaxDownloadRequestMem = 128 * Megabyte + + // Any lower than this and we may fail to download messages. + const MinDownloadRequestMem = 40 * Megabyte + + // This value can be increased to your hearts content. The more system memory the user has, the more messages + // we can build in parallel. + const MaxMessageBuildingMem = 128 * Megabyte + const MinMessageBuildingMem = 64 * Megabyte + + // Maximum recommend value for parallel downloads by the API team. + const maxParallelDownloads = 20 + + totalMemory := memory.TotalMemory() + + if maxSyncMemory >= totalMemory/2 { + logrus.Warnf("Requested max sync memory of %v MB is greater than half of system memory (%v MB), forcing to half of system memory", + maxSyncMemory, toMB(totalMemory/2)) + maxSyncMemory = totalMemory / 2 } + if maxSyncMemory < 800*Megabyte { + logrus.Warnf("Requested max sync memory of %v MB, but minimum recommended is 800 MB, forcing max syncMemory to 800MB", toMB(maxSyncMemory)) + maxSyncMemory = 800 * Megabyte + } + + logrus.Debugf("Total System Memory: %v", toMB(totalMemory)) + + syncMaxDownloadRequestMem := MaxDownloadRequestMem + syncMaxMessageBuildingMem := MaxMessageBuildingMem + + // If less than 2GB available try and limit max memory to 512 MB + switch { + case maxSyncMemory < 2*Gigabyte: + if maxSyncMemory < 800*Megabyte { + logrus.Warnf("System has less than 800MB of memory, you may experience issues sycing large mailboxes") + } + syncMaxDownloadRequestMem = MinDownloadRequestMem + syncMaxMessageBuildingMem = MinMessageBuildingMem + case maxSyncMemory == 2*Gigabyte: + // Increasing the max download capacity has very little effect on sync speed. We could increase the download + // memory but the user would see less sync notifications. A smaller value here leads to more frequent + // updates. Additionally, most of ot sync time is spent in the message building. + syncMaxDownloadRequestMem = MaxDownloadRequestMem + // Currently limited so that if a user has multiple accounts active it also doesn't cause excessive memory usage. + syncMaxMessageBuildingMem = MaxMessageBuildingMem + default: + // Divide by 8 as download stage and build stage will use aprox. 4x the specified memory. + remainingMemory := (maxSyncMemory - 2*Gigabyte) / 8 + syncMaxDownloadRequestMem = MaxDownloadRequestMem + remainingMemory + syncMaxMessageBuildingMem = MaxMessageBuildingMem + remainingMemory + } + + logrus.Debugf("Max memory usage for sync Download=%vMB Building=%vMB Predicted Max Total=%vMB", + toMB(syncMaxDownloadRequestMem), + toMB(syncMaxMessageBuildingMem), + toMB((syncMaxMessageBuildingMem*4)+(syncMaxDownloadRequestMem*4)), + ) + + type flushUpdate struct { + messageID string + err error + batchLen int + } + + type downloadRequest struct { + ids []string + expectedSize uint64 + err error + } + + type downloadedMessageBatch struct { + batch []proton.FullMessage + } + + type builtMessageBatch struct { + batch []*buildRes + } + + downloadCh := make(chan downloadRequest) + + buildCh := make(chan downloadedMessageBatch) + // The higher this value, the longer we can continue our download iteration before being blocked on channel writes // to the update flushing goroutine. - flushCh := make(chan []*buildRes, 2) + flushCh := make(chan builtMessageBatch) - // Allow up to 4 batched wait requests. - flushUpdateCh := make(chan flushUpdate, 4) + flushUpdateCh := make(chan flushUpdate) - errorCh := make(chan error, syncWorkers) + errorCh := make(chan error, maxParallelDownloads*4) + + // Go routine in charge of downloading message metadata + logging.GoAnnotated(ctx, func(ctx context.Context) { + defer close(downloadCh) + const MetadataDataPageSize = 150 + + var downloadReq downloadRequest + downloadReq.ids = make([]string, 0, MetadataDataPageSize) + + metadataChunks := xslices.Chunk(messageIDs, MetadataDataPageSize) + for i, metadataChunk := range metadataChunks { + logrus.Debugf("Metadata Request (%v of %v), previous: %v", i, len(metadataChunks), len(downloadReq.ids)) + metadata, err := client.GetMessageMetadataPage(ctx, 0, len(metadataChunk), proton.MessageFilter{ID: metadataChunk}) + if err != nil { + downloadReq.err = err + select { + case downloadCh <- downloadReq: + case <-ctx.Done(): + return + } + return + } + + if ctx.Err() != nil { + return + } + + // Build look up table so that messages are processed in the same order. + metadataMap := make(map[string]int, len(metadata)) + for i, v := range metadata { + metadataMap[v.ID] = i + } + + for i, id := range metadataChunk { + m := &metadata[metadataMap[id]] + nextSize := downloadReq.expectedSize + uint64(m.Size) + if nextSize >= syncMaxDownloadRequestMem || len(downloadReq.ids) >= 256 { + logrus.Debugf("Download Request Sent at %v of %v", i, len(metadata)) + select { + case downloadCh <- downloadReq: + + case <-ctx.Done(): + return + } + downloadReq.expectedSize = 0 + downloadReq.ids = make([]string, 0, MetadataDataPageSize) + nextSize = uint64(m.Size) + } + downloadReq.ids = append(downloadReq.ids, id) + downloadReq.expectedSize = nextSize + } + } + + if len(downloadReq.ids) != 0 { + logrus.Debugf("Sending remaining download request") + select { + case downloadCh <- downloadReq: + + case <-ctx.Done(): + return + } + } + }, logging.Labels{"sync-stage": "meta-data"}) // Goroutine in charge of downloading and building messages in maxBatchSize batches. - go func() { - defer close(flushCh) + logging.GoAnnotated(ctx, func(ctx context.Context) { + defer close(buildCh) defer close(errorCh) + defer func() { + logrus.Debugf("sync downloader exit") + }() + + attachmentDownloader := newAttachmentDownloader(ctx, client, maxParallelDownloads) + defer attachmentDownloader.close() + + for request := range downloadCh { + logrus.Debugf("Download request: %v MB:%v", len(request.ids), toMB(request.expectedSize)) + if request.err != nil { + errorCh <- request.err + return + } - for _, batch := range xslices.Chunk(messageIDs, maxBatchSize) { if ctx.Err() != nil { errorCh <- ctx.Err() return } - result, err := parallel.MapContext(ctx, syncWorkers, batch, func(ctx context.Context, id string) (*buildRes, error) { - msg, err := client.GetFullMessage(ctx, id) + result, err := parallel.MapContext(ctx, maxParallelDownloads, request.ids, func(ctx context.Context, id string) (proton.FullMessage, error) { + var result proton.FullMessage + + msg, err := client.GetMessage(ctx, id) if err != nil { - return nil, err + return proton.FullMessage{}, err } - if ctx.Err() != nil { - return nil, ctx.Err() + attachments, err := attachmentDownloader.getAttachments(ctx, msg.Attachments) + if err != nil { + return proton.FullMessage{}, err } - return buildRFC822(apiLabels, msg, addrKRs[msg.AddressID]), nil + result.Message = msg + result.AttData = attachments + + return result, nil }) if err != nil { errorCh <- err return } + select { + case buildCh <- downloadedMessageBatch{ + batch: result, + }: + + case <-ctx.Done(): + return + } + } + }, logging.Labels{"sync-stage": "download"}) + + // Goroutine which builds messages after they have been downloaded + logging.GoAnnotated(ctx, func(ctx context.Context) { + defer close(flushCh) + defer func() { + logrus.Debugf("sync builder exit") + }() + + maxMessagesInParallel := runtime.NumCPU() + + for buildBatch := range buildCh { if ctx.Err() != nil { - errorCh <- ctx.Err() return } - flushCh <- result + chunks := chunkSyncBuilderBatch(buildBatch.batch, syncMaxMessageBuildingMem) + + for index, chunk := range chunks { + logrus.Debugf("Build request: %v of %v count=%v", index, len(chunks), len(chunk)) + + result, err := parallel.MapContext(ctx, maxMessagesInParallel, chunk, func(ctx context.Context, msg proton.FullMessage) (*buildRes, error) { + return buildRFC822(apiLabels, msg, addrKRs[msg.AddressID], new(bytes.Buffer)), nil + }) + if err != nil { + return + } + + select { + case flushCh <- builtMessageBatch{result}: + + case <-ctx.Done(): + return + } + } } - }() + }, logging.Labels{"sync-stage": "builder"}) // Goroutine which converts the messages into updates and builds a waitable structure for progress tracking. - go func() { + logging.GoAnnotated(ctx, func(ctx context.Context) { defer close(flushUpdateCh) - for batch := range flushCh { - for _, res := range batch { + defer func() { + logrus.Debugf("sync flush exit") + }() + + type updateTargetInfo struct { + queueIndex int + ch *queue.QueuedChannel[imap.Update] + } + + pendingUpdates := make([][]*imap.MessageCreated, len(updateCh)) + addressToIndex := make(map[string]updateTargetInfo) + + { + i := 0 + for addrID, updateCh := range updateCh { + addressToIndex[addrID] = updateTargetInfo{ + ch: updateCh, + queueIndex: i, + } + i++ + } + } + + for downloadBatch := range flushCh { + logrus.Debugf("Flush batch: %v", len(downloadBatch.batch)) + for _, res := range downloadBatch.batch { if res.err != nil { if err := vault.AddFailedMessageID(res.messageID); err != nil { logrus.WithError(err).Error("Failed to add failed message ID") @@ -327,31 +574,38 @@ func syncMessages( } } - flushers[res.addressID].push(res.update) + targetInfo := addressToIndex[res.addressID] + pendingUpdates[targetInfo.queueIndex] = append(pendingUpdates[targetInfo.queueIndex], res.update) } - var pushedUpdates []imap.Update - for _, flusher := range flushers { - flusher.flush() - pushedUpdates = append(pushedUpdates, flusher.collectPushedUpdates()...) + for _, info := range addressToIndex { + up := imap.NewMessagesCreated(true, pendingUpdates[info.queueIndex]...) + info.ch.Enqueue(up) + + err, ok := up.WaitContext(ctx) + if ok && err != nil { + flushUpdateCh <- flushUpdate{ + err: fmt.Errorf("failed to apply sync update to gluon %v: %w", up.String(), err), + } + return + } + + pendingUpdates[info.queueIndex] = pendingUpdates[info.queueIndex][:0] } - flushUpdateCh <- flushUpdate{ - messageID: batch[0].messageID, - pushedUpdates: pushedUpdates, - batchLen: len(batch), + select { + case flushUpdateCh <- flushUpdate{ + messageID: downloadBatch.batch[0].messageID, + err: nil, + batchLen: len(downloadBatch.batch), + }: + case <-ctx.Done(): + return } } - }() + }, logging.Labels{"sync-stage": "flush"}) for flushUpdate := range flushUpdateCh { - for _, up := range flushUpdate.pushedUpdates { - err, ok := up.WaitContext(ctx) - if ok && err != nil { - return fmt.Errorf("failed to apply sync update to gluon %v: %w", up.String(), err) - } - } - if err := vault.SetLastMessageID(flushUpdate.messageID); err != nil { return fmt.Errorf("failed to set last synced message ID: %w", err) } @@ -394,6 +648,9 @@ func newSystemMailboxCreatedUpdate(labelID imap.MailboxID, labelName string) *im case proton.StarredLabel: attrs = attrs.Add(imap.AttrFlagged) + + case proton.AllScheduledLabel: + labelName = "Scheduled" // API actual name is "All Scheduled" } return imap.NewMailboxCreated(imap.Mailbox{ @@ -407,7 +664,7 @@ func newSystemMailboxCreatedUpdate(labelID imap.MailboxID, labelName string) *im func newPlaceHolderMailboxCreatedUpdate(labelName string) *imap.MailboxCreated { return imap.NewMailboxCreated(imap.Mailbox{ - ID: imap.MailboxID(uuid.NewString()), + ID: imap.MailboxID(labelName), Name: []string{labelName}, Flags: defaultFlags, PermanentFlags: defaultPermanentFlags, @@ -456,6 +713,9 @@ func wantLabel(label proton.Label) bool { case proton.StarredLabel: return true + case proton.AllScheduledLabel: + return true + default: return false } @@ -471,3 +731,126 @@ func wantLabels(apiLabels map[string]proton.Label, labelIDs []string) []string { return wantLabel(apiLabel) }) } + +type attachmentResult struct { + attachment []byte + err error +} + +type attachmentJob struct { + id string + size int64 + result chan attachmentResult +} + +type attachmentDownloader struct { + workerCh chan attachmentJob + cancel context.CancelFunc +} + +func attachmentWorker(ctx context.Context, client *proton.Client, work <-chan attachmentJob) { + for { + select { + case <-ctx.Done(): + return + case job, ok := <-work: + if !ok { + return + } + var b bytes.Buffer + b.Grow(int(job.size)) + err := client.GetAttachmentInto(ctx, job.id, &b) + select { + case <-ctx.Done(): + close(job.result) + return + case job.result <- attachmentResult{attachment: b.Bytes(), err: err}: + close(job.result) + } + } + } +} + +func newAttachmentDownloader(ctx context.Context, client *proton.Client, workerCount int) *attachmentDownloader { + workerCh := make(chan attachmentJob, (workerCount+2)*workerCount) + ctx, cancel := context.WithCancel(ctx) + for i := 0; i < workerCount; i++ { + workerCh = make(chan attachmentJob) + logging.GoAnnotated(ctx, func(ctx context.Context) { attachmentWorker(ctx, client, workerCh) }, logging.Labels{ + "sync": fmt.Sprintf("att-downloader %v", i), + }) + } + + return &attachmentDownloader{ + workerCh: workerCh, + cancel: cancel, + } +} + +func (a *attachmentDownloader) getAttachments(ctx context.Context, attachments []proton.Attachment) ([][]byte, error) { + resultChs := make([]chan attachmentResult, len(attachments)) + for i, id := range attachments { + resultChs[i] = make(chan attachmentResult, 1) + select { + case a.workerCh <- attachmentJob{id: id.ID, result: resultChs[i], size: id.Size}: + case <-ctx.Done(): + return nil, ctx.Err() + } + } + + result := make([][]byte, len(attachments)) + var err error + for i := 0; i < len(attachments); i++ { + select { + case <-ctx.Done(): + return nil, ctx.Err() + case r := <-resultChs[i]: + if r.err != nil { + err = fmt.Errorf("failed to get attachment %v: %w", attachments[i], r.err) + } + result[i] = r.attachment + } + } + + return result, err +} + +func (a *attachmentDownloader) close() { + a.cancel() +} + +func chunkSyncBuilderBatch(batch []proton.FullMessage, maxMemory uint64) [][]proton.FullMessage { + var expectedMemUsage uint64 + var chunks [][]proton.FullMessage + var lastIndex int + var index int + + for _, v := range batch { + var dataSize uint64 + for _, a := range v.Attachments { + dataSize += uint64(a.Size) + } + + // 2x increase for attachment due to extra memory needed for decrypting and writing + // in memory buffer. + dataSize *= 2 + dataSize += uint64(len(v.Body)) + + nextMemSize := expectedMemUsage + dataSize + if nextMemSize >= maxMemory { + chunks = append(chunks, batch[lastIndex:index]) + lastIndex = index + expectedMemUsage = dataSize + } else { + expectedMemUsage = nextMemSize + } + + index++ + } + + if lastIndex < len(batch) { + chunks = append(chunks, batch[lastIndex:]) + } + + return chunks +} diff --git a/internal/user/sync_build.go b/internal/user/sync_build.go index e3d87a5e..93309c35 100644 --- a/internal/user/sync_build.go +++ b/internal/user/sync_build.go @@ -48,16 +48,18 @@ func defaultJobOpts() message.JobOptions { } } -func buildRFC822(apiLabels map[string]proton.Label, full proton.FullMessage, addrKR *crypto.KeyRing) *buildRes { +func buildRFC822(apiLabels map[string]proton.Label, full proton.FullMessage, addrKR *crypto.KeyRing, buffer *bytes.Buffer) *buildRes { var ( update *imap.MessageCreated err error ) - if literal, buildErr := message.BuildRFC822(addrKR, full.Message, full.AttData, defaultJobOpts()); buildErr != nil { + buffer.Grow(full.Size) + + if buildErr := message.BuildRFC822Into(addrKR, full.Message, full.AttData, defaultJobOpts(), buffer); buildErr != nil { update = newMessageCreatedFailedUpdate(apiLabels, full.MessageMetadata, buildErr) err = buildErr - } else if created, parseErr := newMessageCreatedUpdate(apiLabels, full.MessageMetadata, literal); parseErr != nil { + } else if created, parseErr := newMessageCreatedUpdate(apiLabels, full.MessageMetadata, buffer.Bytes()); parseErr != nil { update = newMessageCreatedFailedUpdate(apiLabels, full.MessageMetadata, parseErr) err = parseErr } else { diff --git a/internal/user/sync_build_test.go b/internal/user/sync_build_test.go index f52f5ef9..3f0aff2e 100644 --- a/internal/user/sync_build_test.go +++ b/internal/user/sync_build_test.go @@ -24,6 +24,8 @@ import ( "github.com/ProtonMail/gluon/imap" "github.com/ProtonMail/gluon/rfc822" + "github.com/ProtonMail/go-proton-api" + "github.com/bradenaw/juniper/xslices" "github.com/stretchr/testify/require" ) @@ -47,3 +49,32 @@ func TestNewFailedMessageLiteral(t *testing.T) { require.Equal(t, `("text" "plain" () NIL NIL "base64" 114 2)`, parsed.Body) require.Equal(t, `("text" "plain" () NIL NIL "base64" 114 2 NIL NIL NIL NIL)`, parsed.Structure) } + +func TestSyncChunkSyncBuilderBatch(t *testing.T) { + // GODT-2424 - Some messages were not fully built due to a bug in the chunking if the total memory used by the + // message would be higher than the maximum we allowed. + const totalMessageCount = 100 + + msg := proton.FullMessage{ + Message: proton.Message{ + Attachments: []proton.Attachment{ + { + Size: int64(8 * Megabyte), + }, + }, + }, + AttData: nil, + } + + messages := xslices.Repeat(msg, totalMessageCount) + + chunks := chunkSyncBuilderBatch(messages, 16*Megabyte) + + var totalMessagesInChunks int + + for _, v := range chunks { + totalMessagesInChunks += len(v) + } + + require.Equal(t, totalMessagesInChunks, totalMessageCount) +} diff --git a/internal/user/sync_flusher.go b/internal/user/sync_flusher.go deleted file mode 100644 index 0c0dd2dd..00000000 --- a/internal/user/sync_flusher.go +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright (c) 2023 Proton AG -// -// This file is part of Proton Mail Bridge. -// -// Proton Mail Bridge is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Proton Mail Bridge is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Proton Mail Bridge. If not, see . - -package user - -import ( - "github.com/ProtonMail/gluon/imap" - "github.com/ProtonMail/gluon/queue" -) - -type flusher struct { - updateCh *queue.QueuedChannel[imap.Update] - updates []*imap.MessageCreated - pushedUpdates []imap.Update - - maxUpdateSize int - curChunkSize int -} - -func newFlusher(updateCh *queue.QueuedChannel[imap.Update], maxUpdateSize int) *flusher { - return &flusher{ - updateCh: updateCh, - maxUpdateSize: maxUpdateSize, - } -} - -func (f *flusher) push(update *imap.MessageCreated) { - f.updates = append(f.updates, update) - - if f.curChunkSize += len(update.Literal); f.curChunkSize >= f.maxUpdateSize { - f.flush() - } -} - -func (f *flusher) flush() { - if len(f.updates) > 0 { - update := imap.NewMessagesCreated(true, f.updates...) - f.updateCh.Enqueue(update) - f.updates = nil - f.curChunkSize = 0 - f.pushedUpdates = append(f.pushedUpdates, update) - } -} - -func (f *flusher) collectPushedUpdates() []imap.Update { - updates := f.pushedUpdates - f.pushedUpdates = nil - return updates -} diff --git a/internal/user/types.go b/internal/user/types.go index 254bf8b9..09478f5e 100644 --- a/internal/user/types.go +++ b/internal/user/types.go @@ -20,6 +20,7 @@ package user import ( "fmt" "reflect" + "runtime" "strings" "github.com/ProtonMail/go-proton-api" @@ -91,3 +92,7 @@ func sortSlice[Item any](items []Item, less func(Item, Item) bool) []Item { return sorted } + +func newProtonAPIScheduler() proton.Scheduler { + return proton.NewParallelScheduler(runtime.NumCPU() / 2) +} diff --git a/internal/user/user.go b/internal/user/user.go index e3b8a8cb..710d7805 100644 --- a/internal/user/user.go +++ b/internal/user/user.go @@ -88,13 +88,12 @@ type User struct { pollAPIEventsCh chan chan struct{} goPollAPIEvents func(wait bool) - syncWorkers int showAllMail uint32 + + maxSyncMemory uint64 } // New returns a new user. -// -// nolint:funlen func New( ctx context.Context, encVault *vault.User, @@ -102,9 +101,9 @@ func New( reporter reporter.Reporter, apiUser proton.User, crashHandler async.PanicHandler, - syncWorkers int, showAllMail bool, -) (*User, error) { //nolint:funlen + maxSyncMemory uint64, +) (*User, error) { logrus.WithField("userID", apiUser.ID).Info("Creating new user") // Get the user's API addresses. @@ -146,8 +145,9 @@ func New( tasks: async.NewGroup(context.Background(), crashHandler), pollAPIEventsCh: make(chan chan struct{}), - syncWorkers: syncWorkers, showAllMail: b32(showAllMail), + + maxSyncMemory: maxSyncMemory, } // Initialize the user's update channels for its current address mode. @@ -193,7 +193,15 @@ func New( // Sync the user. user.syncAbort.Do(ctx, func(ctx context.Context) { if user.vault.SyncStatus().IsComplete() { - user.log.Info("Sync already complete, skipping") + user.log.Info("Sync already complete, only system label will be updated") + + if err := user.syncSystemLabels(ctx); err != nil { + user.log.WithError(err).Error("Failed to update system labels") + return + } + + user.log.Info("System label update complete, starting API event stream") + return } @@ -257,11 +265,13 @@ func (user *User) Match(query string) bool { }, user.apiUserLock, user.apiAddrsLock) } -// Emails returns all the user's email addresses. +// Emails returns all the user's active email addresses. // It returns them in sorted order; the user's primary address is first. func (user *User) Emails() []string { return safe.RLockRet(func() []string { - addresses := maps.Values(user.apiAddrs) + addresses := xslices.Filter(maps.Values(user.apiAddrs), func(addr proton.Address) bool { + return addr.Status == proton.AddressStatusEnabled + }) slices.SortFunc(addresses, func(a, b proton.Address) bool { return a.Order < b.Order @@ -432,8 +442,6 @@ func (user *User) NewIMAPConnectors() (map[string]connector.Connector, error) { } // SendMail sends an email from the given address to the given recipients. -// -// nolint:funlen func (user *User) SendMail(authID string, from string, to []string, r io.Reader) error { if user.vault.SyncStatus().IsComplete() { defer user.goPollAPIEvents(true) @@ -636,13 +644,11 @@ func (user *User) startEvents(ctx context.Context) { } // doEventPoll is called whenever API events should be polled. -// -//nolint:funlen func (user *User) doEventPoll(ctx context.Context) error { user.eventLock.Lock() defer user.eventLock.Unlock() - event, err := user.client.GetEvent(ctx, user.vault.EventID()) + event, _, err := user.client.GetEvent(ctx, user.vault.EventID()) if err != nil { return fmt.Errorf("failed to get event (caused by %T): %w", internal.ErrCause(err), err) } diff --git a/internal/user/user_test.go b/internal/user/user_test.go index ad220f6e..a9ac6e04 100644 --- a/internal/user/user_test.go +++ b/internal/user/user_test.go @@ -119,14 +119,14 @@ func withUser(tb testing.TB, ctx context.Context, _ *server.Server, m *proton.Ma saltedKeyPass, err := salts.SaltForKey([]byte(password), apiUser.Keys.Primary().ID) require.NoError(tb, err) - vault, corrupt, err := vault.New(tb.TempDir(), tb.TempDir(), []byte("my secret key")) + v, corrupt, err := vault.New(tb.TempDir(), tb.TempDir(), []byte("my secret key")) require.NoError(tb, err) require.False(tb, corrupt) - vaultUser, err := vault.AddUser(apiUser.ID, username, username+"@pm.me", apiAuth.UID, apiAuth.RefreshToken, saltedKeyPass) + vaultUser, err := v.AddUser(apiUser.ID, username, username+"@pm.me", apiAuth.UID, apiAuth.RefreshToken, saltedKeyPass) require.NoError(tb, err) - user, err := New(ctx, vaultUser, client, nil, apiUser, nil, vault.SyncWorkers(), true) + user, err := New(ctx, vaultUser, client, nil, apiUser, nil, true, vault.DefaultMaxSyncMemory) require.NoError(tb, err) defer user.Close() diff --git a/internal/vault/certs.go b/internal/vault/certs.go index 77086f9b..75294293 100644 --- a/internal/vault/certs.go +++ b/internal/vault/certs.go @@ -17,12 +17,40 @@ package vault -func (vault *Vault) GetBridgeTLSCert() []byte { - return vault.get().Certs.Bridge.Cert +import ( + "crypto/tls" + "fmt" + "os" + "path/filepath" + + "github.com/sirupsen/logrus" +) + +// GetBridgeTLSCert returns the PEM-encoded certificate for the bridge. +// If CertPEMPath is set, it will attempt to read the certificate from the file. +// Otherwise, or on read/validation failure, it will return the certificate from the vault. +func (vault *Vault) GetBridgeTLSCert() ([]byte, []byte) { + if certPath, keyPath := vault.get().Certs.CustomCertPath, vault.get().Certs.CustomKeyPath; certPath != "" && keyPath != "" { + if certPEM, keyPEM, err := readPEMCert(certPath, keyPath); err == nil { + return certPEM, keyPEM + } + + logrus.Error("Failed to read certificate from file, using default") + } + + return vault.get().Certs.Bridge.Cert, vault.get().Certs.Bridge.Key } -func (vault *Vault) GetBridgeTLSKey() []byte { - return vault.get().Certs.Bridge.Key +// SetBridgeTLSCertPath sets the path to PEM-encoded certificates for the bridge. +func (vault *Vault) SetBridgeTLSCertPath(certPath, keyPath string) error { + if _, _, err := readPEMCert(certPath, keyPath); err != nil { + return fmt.Errorf("invalid certificate: %w", err) + } + + return vault.mod(func(data *Data) { + data.Certs.CustomCertPath = certPath + data.Certs.CustomKeyPath = keyPath + }) } func (vault *Vault) GetCertsInstalled() bool { @@ -34,3 +62,21 @@ func (vault *Vault) SetCertsInstalled(installed bool) error { data.Certs.Installed = installed }) } + +func readPEMCert(certPEMPath, keyPEMPath string) ([]byte, []byte, error) { + certPEM, err := os.ReadFile(filepath.Clean(certPEMPath)) + if err != nil { + return nil, nil, err + } + + keyPEM, err := os.ReadFile(filepath.Clean(keyPEMPath)) + if err != nil { + return nil, nil, err + } + + if _, err := tls.X509KeyPair(certPEM, keyPEM); err != nil { + return nil, nil, err + } + + return certPEM, keyPEM, nil +} diff --git a/internal/vault/certs_test.go b/internal/vault/certs_test.go index 243b575f..0a3d7fde 100644 --- a/internal/vault/certs_test.go +++ b/internal/vault/certs_test.go @@ -28,8 +28,9 @@ func TestVault_TLSCerts(t *testing.T) { s := newVault(t) // Check the default bridge TLS certs. - require.NotEmpty(t, s.GetBridgeTLSCert()) - require.NotEmpty(t, s.GetBridgeTLSKey()) + cert, key := s.GetBridgeTLSCert() + require.NotEmpty(t, cert) + require.NotEmpty(t, key) // Check the certificates are not installed. require.False(t, s.GetCertsInstalled()) diff --git a/internal/vault/helper.go b/internal/vault/helper.go index 721c5ab0..dfc64878 100644 --- a/internal/vault/helper.go +++ b/internal/vault/helper.go @@ -18,13 +18,21 @@ package vault import ( + "encoding/base64" "encoding/json" "errors" + "fmt" "io/fs" "os" "path/filepath" + + "github.com/ProtonMail/gopenpgp/v2/crypto" + "github.com/ProtonMail/proton-bridge/v3/pkg/keychain" + "golang.org/x/exp/slices" ) +const vaultSecretName = "bridge-vault-key" + type Keychain struct { Helper string } @@ -60,3 +68,43 @@ func SetHelper(vaultDir, helper string) error { return os.WriteFile(getKeychainPrefPath(vaultDir), b, 0o600) } + +func HasVaultKey(kc *keychain.Keychain) (bool, error) { + secrets, err := kc.List() + if err != nil { + return false, fmt.Errorf("could not list keychain: %w", err) + } + + return slices.Contains(secrets, vaultSecretName), nil +} + +func GetVaultKey(kc *keychain.Keychain) ([]byte, error) { + _, keyEnc, err := kc.Get(vaultSecretName) + if err != nil { + return nil, fmt.Errorf("could not get keychain item: %w", err) + } + + keyDec, err := base64.StdEncoding.DecodeString(keyEnc) + if err != nil { + return nil, fmt.Errorf("could not decode keychain item: %w", err) + } + + return keyDec, nil +} + +func SetVaultKey(kc *keychain.Keychain, key []byte) error { + return kc.Put(vaultSecretName, base64.StdEncoding.EncodeToString(key)) +} + +func NewVaultKey(kc *keychain.Keychain) ([]byte, error) { + tok, err := crypto.RandomToken(32) + if err != nil { + return nil, fmt.Errorf("could not generate random token: %w", err) + } + + if err := kc.Put(vaultSecretName, base64.StdEncoding.EncodeToString(tok)); err != nil { + return nil, fmt.Errorf("could not put keychain item: %w", err) + } + + return tok, nil +} diff --git a/internal/vault/settings.go b/internal/vault/settings.go index f28cc40f..9967e307 100644 --- a/internal/vault/settings.go +++ b/internal/vault/settings.go @@ -196,7 +196,7 @@ func (vault *Vault) SetLastVersion(version *semver.Version) error { }) } -// GetFirstStart sets whether this is the first time the bridge has been started. +// GetFirstStart returns whether this is the first time the bridge has been started. func (vault *Vault) GetFirstStart() bool { return vault.get().Settings.FirstStart } @@ -208,26 +208,20 @@ func (vault *Vault) SetFirstStart(firstStart bool) error { }) } -// SyncWorkers returns the number of workers to use for syncing. -func (vault *Vault) SyncWorkers() int { - return vault.get().Settings.SyncWorkers +// GetMaxSyncMemory returns the maximum amount of memory the sync process should use. +func (vault *Vault) GetMaxSyncMemory() uint64 { + v := vault.get().Settings.MaxSyncMemory + // can be zero if never written to vault before. + if v == 0 { + return DefaultMaxSyncMemory + } + + return v } -// SetSyncWorkers sets the number of workers to use for syncing. -func (vault *Vault) SetSyncWorkers(workers int) error { +// SetMaxSyncMemory sets the maximum amount of memory the sync process should use. +func (vault *Vault) SetMaxSyncMemory(maxMemory uint64) error { return vault.mod(func(data *Data) { - data.Settings.SyncWorkers = workers - }) -} - -// SyncAttPool returns the size of the attachment pool. -func (vault *Vault) SyncAttPool() int { - return vault.get().Settings.SyncAttPool -} - -// SetSyncAttPool sets the size of the attachment pool. -func (vault *Vault) SetSyncAttPool(pool int) error { - return vault.mod(func(data *Data) { - data.Settings.SyncAttPool = pool + data.Settings.MaxSyncMemory = maxMemory }) } diff --git a/internal/vault/settings_test.go b/internal/vault/settings_test.go index 8d0eddab..33528fae 100644 --- a/internal/vault/settings_test.go +++ b/internal/vault/settings_test.go @@ -208,11 +208,10 @@ func TestVault_Settings_FirstStart(t *testing.T) { require.Equal(t, false, s.GetFirstStart()) } -func TestVault_Settings_SyncWorkers(t *testing.T) { +func TestVault_Settings_MaxSyncMemory(t *testing.T) { // create a new test vault. s := newVault(t) - syncWorkers := vault.GetDefaultSyncWorkerCount() - require.Equal(t, syncWorkers, s.SyncWorkers()) - require.Equal(t, syncWorkers, s.SyncAttPool()) + // Check the default first start value. + require.Equal(t, vault.DefaultMaxSyncMemory, s.GetMaxSyncMemory()) } diff --git a/internal/vault/types_certs.go b/internal/vault/types_certs.go index b478199d..195a43a1 100644 --- a/internal/vault/types_certs.go +++ b/internal/vault/types_certs.go @@ -22,6 +22,10 @@ import "github.com/ProtonMail/proton-bridge/v3/internal/certs" type Certs struct { Bridge Cert Installed bool + + // If non-empty, the path to the PEM-encoded certificate file. + CustomCertPath string + CustomKeyPath string } type Cert struct { diff --git a/internal/vault/types_settings.go b/internal/vault/types_settings.go index 47c70654..bb6946a3 100644 --- a/internal/vault/types_settings.go +++ b/internal/vault/types_settings.go @@ -19,7 +19,6 @@ package vault import ( "math/rand" - "runtime" "github.com/ProtonMail/proton-bridge/v3/internal/updater" ) @@ -44,25 +43,12 @@ type Settings struct { LastVersion string FirstStart bool - SyncWorkers int - SyncAttPool int + MaxSyncMemory uint64 } -func GetDefaultSyncWorkerCount() int { - const minSyncWorkers = 16 - - syncWorkers := runtime.NumCPU() * 4 - - if syncWorkers < minSyncWorkers { - syncWorkers = minSyncWorkers - } - - return syncWorkers -} +const DefaultMaxSyncMemory = 2 * 1024 * uint64(1024*1024) func newDefaultSettings(gluonDir string) Settings { - syncWorkers := GetDefaultSyncWorkerCount() - return Settings{ GluonDir: gluonDir, @@ -83,7 +69,6 @@ func newDefaultSettings(gluonDir string) Settings { LastVersion: "0.0.0", FirstStart: true, - SyncWorkers: syncWorkers, - SyncAttPool: syncWorkers, + MaxSyncMemory: DefaultMaxSyncMemory, } } diff --git a/internal/vault/types_user.go b/internal/vault/types_user.go index 0fe118da..af1958e3 100644 --- a/internal/vault/types_user.go +++ b/internal/vault/types_user.go @@ -17,8 +17,6 @@ package vault -import "github.com/ProtonMail/gluon/imap" - // UserData holds information about a single bridge user. // The user may or may not be logged in. type UserData struct { @@ -28,7 +26,6 @@ type UserData struct { GluonKey []byte GluonIDs map[string]string - UIDValidity map[string]imap.UID BridgePass []byte // raw token represented as byte slice (needs to be encoded) AddressMode AddressMode @@ -79,7 +76,6 @@ func newDefaultUser(userID, username, primaryEmail, authUID, authRef string, key GluonKey: newRandomToken(32), GluonIDs: make(map[string]string), - UIDValidity: make(map[string]imap.UID), BridgePass: newRandomToken(16), AddressMode: CombinedMode, diff --git a/internal/vault/user.go b/internal/vault/user.go index 1f3bf8b6..f97358dc 100644 --- a/internal/vault/user.go +++ b/internal/vault/user.go @@ -20,7 +20,6 @@ package vault import ( "fmt" - "github.com/ProtonMail/gluon/imap" "github.com/bradenaw/juniper/xslices" "golang.org/x/exp/slices" ) @@ -81,24 +80,6 @@ func (user *User) RemoveGluonID(addrID, gluonID string) error { return err } -func (user *User) GetUIDValidity(addrID string) imap.UID { - if validity, ok := user.vault.getUser(user.userID).UIDValidity[addrID]; ok { - return validity - } - - if err := user.SetUIDValidity(addrID, 1000); err != nil { - panic(err) - } - - return user.GetUIDValidity(addrID) -} - -func (user *User) SetUIDValidity(addrID string, validity imap.UID) error { - return user.vault.modUser(user.userID, func(data *UserData) { - data.UIDValidity[addrID] = validity - }) -} - // AddressMode returns the user's address mode. func (user *User) AddressMode() AddressMode { return user.vault.getUser(user.userID).AddressMode @@ -208,10 +189,6 @@ func (user *User) ClearSyncStatus() error { data.SyncStatus = SyncStatus{} data.EventID = "" - - for addrID := range data.UIDValidity { - data.UIDValidity[addrID]++ - } }) } diff --git a/internal/vault/vault.go b/internal/vault/vault.go index 09f3b50e..4e605e3a 100644 --- a/internal/vault/vault.go +++ b/internal/vault/vault.go @@ -191,6 +191,10 @@ func (vault *Vault) Reset(gluonDir string) error { }) } +func (vault *Vault) Path() string { + return vault.path +} + func (vault *Vault) Close() error { vault.refLock.Lock() defer vault.refLock.Unlock() diff --git a/pkg/message/build.go b/pkg/message/build.go index f31b20f7..f6bfbc0a 100644 --- a/pkg/message/build.go +++ b/pkg/message/build.go @@ -20,6 +20,7 @@ package message import ( "bytes" "encoding/base64" + "io" "mime" "net/mail" "strings" @@ -46,65 +47,73 @@ var ( const InternalIDDomain = `protonmail.internalid` func BuildRFC822(kr *crypto.KeyRing, msg proton.Message, attData [][]byte, opts JobOptions) ([]byte, error) { - switch { - case len(msg.Attachments) > 0: - return buildMultipartRFC822(kr, msg, attData, opts) - - case msg.MIMEType == "multipart/mixed": - return buildPGPRFC822(kr, msg, opts) - - default: - return buildSimpleRFC822(kr, msg, opts) - } -} - -func buildSimpleRFC822(kr *crypto.KeyRing, msg proton.Message, opts JobOptions) ([]byte, error) { - dec, err := msg.Decrypt(kr) - if err != nil { - if !opts.IgnoreDecryptionErrors { - return nil, errors.Wrap(ErrDecryptionFailed, err.Error()) - } - - return buildMultipartRFC822(kr, msg, nil, opts) - } - - hdr := getTextPartHeader(getMessageHeader(msg, opts), dec, msg.MIMEType) - buf := new(bytes.Buffer) - - w, err := message.CreateWriter(buf, hdr) - if err != nil { - return nil, err - } - - if _, err := w.Write(dec); err != nil { - return nil, err - } - - if err := w.Close(); err != nil { + if err := BuildRFC822Into(kr, msg, attData, opts, buf); err != nil { return nil, err } return buf.Bytes(), nil } +func BuildRFC822Into(kr *crypto.KeyRing, msg proton.Message, attData [][]byte, opts JobOptions, buf *bytes.Buffer) error { + switch { + case len(msg.Attachments) > 0: + return buildMultipartRFC822(kr, msg, attData, opts, buf) + + case msg.MIMEType == "multipart/mixed": + return buildPGPRFC822(kr, msg, opts, buf) + + default: + return buildSimpleRFC822(kr, msg, opts, buf) + } +} + +func buildSimpleRFC822(kr *crypto.KeyRing, msg proton.Message, opts JobOptions, buf *bytes.Buffer) error { + var decrypted bytes.Buffer + decrypted.Grow(len(msg.Body)) + + if err := msg.DecryptInto(kr, &decrypted); err != nil { + if !opts.IgnoreDecryptionErrors { + return errors.Wrap(ErrDecryptionFailed, err.Error()) + } + + return buildMultipartRFC822(kr, msg, nil, opts, buf) + } + + hdr := getTextPartHeader(getMessageHeader(msg, opts), decrypted.Bytes(), msg.MIMEType) + + w, err := message.CreateWriter(buf, hdr) + if err != nil { + return err + } + + if _, err := w.Write(decrypted.Bytes()); err != nil { + return err + } + + if err := w.Close(); err != nil { + return err + } + + return nil +} + func buildMultipartRFC822( kr *crypto.KeyRing, msg proton.Message, attData [][]byte, opts JobOptions, -) ([]byte, error) { + buf *bytes.Buffer, +) error { boundary := newBoundary(msg.ID) hdr := getMessageHeader(msg, opts) hdr.SetContentType("multipart/mixed", map[string]string{"boundary": boundary.gen()}) - buf := new(bytes.Buffer) - w, err := message.CreateWriter(buf, hdr) if err != nil { - return nil, err + return err } var ( @@ -126,23 +135,23 @@ func buildMultipartRFC822( if len(inlineAtts) > 0 { if err := writeRelatedParts(w, kr, boundary, msg, inlineAtts, inlineData, opts); err != nil { - return nil, err + return err } } else if err := writeTextPart(w, kr, msg, opts); err != nil { - return nil, err + return err } for i, att := range attachAtts { if err := writeAttachmentPart(w, kr, att, attachData[i], opts); err != nil { - return nil, err + return err } } if err := w.Close(); err != nil { - return nil, err + return err } - return buf.Bytes(), nil + return nil } func writeTextPart( @@ -151,8 +160,10 @@ func writeTextPart( msg proton.Message, opts JobOptions, ) error { - dec, err := msg.Decrypt(kr) - if err != nil { + var decrypted bytes.Buffer + decrypted.Grow(len(msg.Body)) + + if err := msg.DecryptInto(kr, &decrypted); err != nil { if !opts.IgnoreDecryptionErrors { return errors.Wrap(ErrDecryptionFailed, err.Error()) } @@ -160,7 +171,7 @@ func writeTextPart( return writeCustomTextPart(w, msg, err) } - return writePart(w, getTextPartHeader(message.Header{}, dec, msg.MIMEType), dec) + return writePart(w, getTextPartHeader(message.Header{}, decrypted.Bytes(), msg.MIMEType), decrypted.Bytes()) } func writeAttachmentPart( @@ -175,9 +186,10 @@ func writeAttachmentPart( return err } - msg := crypto.NewPGPSplitMessage(kps, attData).GetPGPMessage() + // Use io.Multi + attachmentReader := io.MultiReader(bytes.NewReader(kps), bytes.NewReader(attData)) - dec, err := kr.Decrypt(msg, nil, crypto.GetUnixTime()) + stream, err := kr.DecryptStream(attachmentReader, nil, crypto.GetUnixTime()) if err != nil { if !opts.IgnoreDecryptionErrors { return errors.Wrap(ErrDecryptionFailed, err.Error()) @@ -186,12 +198,38 @@ func writeAttachmentPart( log. WithField("attID", att.ID). WithError(err). - Warn("Attachment decryption failed") + Warn("Attachment decryption failed - construct") - return writeCustomAttachmentPart(w, att, msg, err) + var pgpMessageBuffer bytes.Buffer + pgpMessageBuffer.Grow(len(kps) + len(attData)) + pgpMessageBuffer.Write(kps) + pgpMessageBuffer.Write(attData) + + return writeCustomAttachmentPart(w, att, &crypto.PGPMessage{Data: pgpMessageBuffer.Bytes()}, err) } - return writePart(w, getAttachmentPartHeader(att), dec.GetBinary()) + var decryptBuffer bytes.Buffer + decryptBuffer.Grow(len(kps) + len(attData)) + + if _, err := decryptBuffer.ReadFrom(stream); err != nil { + if !opts.IgnoreDecryptionErrors { + return errors.Wrap(ErrDecryptionFailed, err.Error()) + } + + log. + WithField("attID", att.ID). + WithError(err). + Warn("Attachment decryption failed - stream") + + var pgpMessageBuffer bytes.Buffer + pgpMessageBuffer.Grow(len(kps) + len(attData)) + pgpMessageBuffer.Write(kps) + pgpMessageBuffer.Write(attData) + + return writeCustomAttachmentPart(w, att, &crypto.PGPMessage{Data: pgpMessageBuffer.Bytes()}, err) + } + + return writePart(w, getAttachmentPartHeader(att), decryptBuffer.Bytes()) } func writeRelatedParts( @@ -222,14 +260,16 @@ func writeRelatedParts( }) } -func buildPGPRFC822(kr *crypto.KeyRing, msg proton.Message, opts JobOptions) ([]byte, error) { - dec, err := msg.Decrypt(kr) - if err != nil { +func buildPGPRFC822(kr *crypto.KeyRing, msg proton.Message, opts JobOptions, buf *bytes.Buffer) error { + var decrypted bytes.Buffer + decrypted.Grow(len(msg.Body)) + + if err := msg.DecryptInto(kr, &decrypted); err != nil { if !opts.IgnoreDecryptionErrors { - return nil, errors.Wrap(ErrDecryptionFailed, err.Error()) + return errors.Wrap(ErrDecryptionFailed, err.Error()) } - return buildPGPMIMEFallbackRFC822(msg, opts) + return buildPGPMIMEFallbackRFC822(msg, opts, buf) } hdr := getMessageHeader(msg, opts) @@ -240,13 +280,13 @@ func buildPGPRFC822(kr *crypto.KeyRing, msg proton.Message, opts JobOptions) ([] } if len(sigs) > 0 { - return writeMultipartSignedRFC822(hdr, dec, sigs[0]) + return writeMultipartSignedRFC822(hdr, decrypted.Bytes(), sigs[0], buf) } - return writeMultipartEncryptedRFC822(hdr, dec) + return writeMultipartEncryptedRFC822(hdr, decrypted.Bytes(), buf) } -func buildPGPMIMEFallbackRFC822(msg proton.Message, opts JobOptions) ([]byte, error) { +func buildPGPMIMEFallbackRFC822(msg proton.Message, opts JobOptions, buf *bytes.Buffer) error { hdr := getMessageHeader(msg, opts) hdr.SetContentType("multipart/encrypted", map[string]string{ @@ -254,11 +294,9 @@ func buildPGPMIMEFallbackRFC822(msg proton.Message, opts JobOptions) ([]byte, er "protocol": "application/pgp-encrypted", }) - buf := new(bytes.Buffer) - w, err := message.CreateWriter(buf, hdr) if err != nil { - return nil, err + return err } var encHdr message.Header @@ -267,7 +305,7 @@ func buildPGPMIMEFallbackRFC822(msg proton.Message, opts JobOptions) ([]byte, er encHdr.Set("Content-Description", "PGP/MIME version identification") if err := writePart(w, encHdr, []byte("Version: 1")); err != nil { - return nil, err + return err } var dataHdr message.Header @@ -277,19 +315,17 @@ func buildPGPMIMEFallbackRFC822(msg proton.Message, opts JobOptions) ([]byte, er dataHdr.Set("Content-Description", "OpenPGP encrypted message") if err := writePart(w, dataHdr, []byte(msg.Body)); err != nil { - return nil, err + return err } if err := w.Close(); err != nil { - return nil, err + return err } - return buf.Bytes(), nil + return nil } -func writeMultipartSignedRFC822(header message.Header, body []byte, sig proton.Signature) ([]byte, error) { //nolint:funlen - buf := new(bytes.Buffer) - +func writeMultipartSignedRFC822(header message.Header, body []byte, sig proton.Signature, buf *bytes.Buffer) error { boundary := newBoundary("").gen() header.SetContentType("multipart/signed", map[string]string{ @@ -299,27 +335,27 @@ func writeMultipartSignedRFC822(header message.Header, body []byte, sig proton.S }) if err := textproto.WriteHeader(buf, header.Header); err != nil { - return nil, err + return err } mw := textproto.NewMultipartWriter(buf) if err := mw.SetBoundary(boundary); err != nil { - return nil, err + return err } bodyHeader, bodyData, err := readHeaderBody(body) if err != nil { - return nil, err + return err } bodyPart, err := mw.CreatePart(*bodyHeader) if err != nil { - return nil, err + return err } if _, err := bodyPart.Write(bodyData); err != nil { - return nil, err + return err } var sigHeader message.Header @@ -330,31 +366,29 @@ func writeMultipartSignedRFC822(header message.Header, body []byte, sig proton.S sigPart, err := mw.CreatePart(sigHeader.Header) if err != nil { - return nil, err + return err } sigData, err := sig.Data.GetArmored() if err != nil { - return nil, err + return err } if _, err := sigPart.Write([]byte(sigData)); err != nil { - return nil, err + return err } if err := mw.Close(); err != nil { - return nil, err + return err } - return buf.Bytes(), nil + return nil } -func writeMultipartEncryptedRFC822(header message.Header, body []byte) ([]byte, error) { - buf := new(bytes.Buffer) - +func writeMultipartEncryptedRFC822(header message.Header, body []byte, buf *bytes.Buffer) error { bodyHeader, bodyData, err := readHeaderBody(body) if err != nil { - return nil, err + return err } // Remove old content type header as it is non-standard. Ensure that messages @@ -371,14 +405,14 @@ func writeMultipartEncryptedRFC822(header message.Header, body []byte) ([]byte, } if err := textproto.WriteHeader(buf, header.Header); err != nil { - return nil, err + return err } if _, err := buf.Write(bodyData); err != nil { - return nil, err + return err } - return buf.Bytes(), nil + return nil } func addressEmpty(address *mail.Address) bool { @@ -393,7 +427,7 @@ func addressEmpty(address *mail.Address) bool { return false } -func getMessageHeader(msg proton.Message, opts JobOptions) message.Header { //nolint:funlen +func getMessageHeader(msg proton.Message, opts JobOptions) message.Header { hdr := toMessageHeader(msg.ParsedHeaders) // SetText will RFC2047-encode. @@ -445,7 +479,9 @@ func getMessageHeader(msg proton.Message, opts JobOptions) message.Header { //no // Set our external ID if requested. // This was useful during debugging of applemail recovered messages; doesn't help with any behaviour. if opts.AddExternalID { - hdr.Set("X-Pm-External-Id", "<"+msg.ExternalID+">") + if msg.ExternalID != "" { + hdr.Set("X-Pm-External-Id", "<"+msg.ExternalID+">") + } } // Set our server date if requested. diff --git a/pkg/message/parser.go b/pkg/message/parser.go index 6d5ee23c..cba83aee 100644 --- a/pkg/message/parser.go +++ b/pkg/message/parser.go @@ -433,7 +433,7 @@ func getPlainBody(part *parser.Part) []byte { } } -func parseMessageHeader(h message.Header) (Message, error) { //nolint:funlen +func parseMessageHeader(h message.Header) (Message, error) { var m Message for fields := h.Fields(); fields.Next(); { diff --git a/pkg/mime/encoding.go b/pkg/mime/encoding.go index 70b21518..8fa8edcf 100644 --- a/pkg/mime/encoding.go +++ b/pkg/mime/encoding.go @@ -25,6 +25,7 @@ import ( "strings" "unicode/utf8" + "github.com/ProtonMail/gluon/rfc5322" "github.com/ProtonMail/gluon/rfc822" "github.com/ProtonMail/go-proton-api" "github.com/pkg/errors" @@ -37,6 +38,7 @@ import ( func init() { rfc822.ParseMediaType = ParseMediaType proton.CharsetReader = CharsetReader + rfc5322.CharsetReader = CharsetReader } func CharsetReader(charset string, input io.Reader) (io.Reader, error) { diff --git a/tests/bdd_test.go b/tests/bdd_test.go index 2fcb1e5d..f67caa70 100644 --- a/tests/bdd_test.go +++ b/tests/bdd_test.go @@ -162,7 +162,6 @@ func TestFeatures(testingT *testing.T) { ctx.Step(`^user "([^"]*)" logs out$`, s.userLogsOut) ctx.Step(`^user "([^"]*)" is deleted$`, s.userIsDeleted) ctx.Step(`^the auth of user "([^"]*)" is revoked$`, s.theAuthOfUserIsRevoked) - ctx.Step(`^user "([^"]*)" is listed and connected$`, s.userIsListedAndConnected) ctx.Step(`^user "([^"]*)" is eventually listed and connected$`, s.userIsEventuallyListedAndConnected) ctx.Step(`^user "([^"]*)" is listed but not connected$`, s.userIsListedButNotConnected) ctx.Step(`^user "([^"]*)" is not listed$`, s.userIsNotListed) @@ -184,7 +183,6 @@ func TestFeatures(testingT *testing.T) { ctx.Step(`^IMAP client "([^"]*)" creates "([^"]*)"$`, s.imapClientCreatesMailbox) ctx.Step(`^IMAP client "([^"]*)" deletes "([^"]*)"$`, s.imapClientDeletesMailbox) ctx.Step(`^IMAP client "([^"]*)" renames "([^"]*)" to "([^"]*)"$`, s.imapClientRenamesMailboxTo) - ctx.Step(`^IMAP client "([^"]*)" sees the following mailbox info:$`, s.imapClientSeesTheFollowingMailboxInfo) ctx.Step(`^IMAP client "([^"]*)" eventually sees the following mailbox info:$`, s.imapClientEventuallySeesTheFollowingMailboxInfo) ctx.Step(`^IMAP client "([^"]*)" sees the following mailbox info for "([^"]*)":$`, s.imapClientSeesTheFollowingMailboxInfoForMailbox) ctx.Step(`^IMAP client "([^"]*)" sees "([^"]*)"$`, s.imapClientSeesMailbox) @@ -195,9 +193,7 @@ func TestFeatures(testingT *testing.T) { ctx.Step(`^IMAP client "([^"]*)" copies all messages from "([^"]*)" to "([^"]*)"$`, s.imapClientCopiesAllMessagesFromTo) ctx.Step(`^IMAP client "([^"]*)" moves the message with subject "([^"]*)" from "([^"]*)" to "([^"]*)"$`, s.imapClientMovesTheMessageWithSubjectFromTo) ctx.Step(`^IMAP client "([^"]*)" moves all messages from "([^"]*)" to "([^"]*)"$`, s.imapClientMovesAllMessagesFromTo) - ctx.Step(`^IMAP client "([^"]*)" sees the following messages in "([^"]*)":$`, s.imapClientSeesTheFollowingMessagesInMailbox) ctx.Step(`^IMAP client "([^"]*)" eventually sees the following messages in "([^"]*)":$`, s.imapClientEventuallySeesTheFollowingMessagesInMailbox) - ctx.Step(`^IMAP client "([^"]*)" sees (\d+) messages in "([^"]*)"$`, s.imapClientSeesMessagesInMailbox) ctx.Step(`^IMAP client "([^"]*)" eventually sees (\d+) messages in "([^"]*)"$`, s.imapClientEventuallySeesMessagesInMailbox) ctx.Step(`^IMAP client "([^"]*)" marks message (\d+) as deleted$`, s.imapClientMarksMessageAsDeleted) ctx.Step(`^IMAP client "([^"]*)" marks the message with subject "([^"]*)" as deleted$`, s.imapClientMarksTheMessageWithSubjectAsDeleted) diff --git a/tests/bridge_test.go b/tests/bridge_test.go index 9e57773a..45f6e67a 100644 --- a/tests/bridge_test.go +++ b/tests/bridge_test.go @@ -68,10 +68,10 @@ func (s *scenario) theUserChangesTheSMTPPortTo(port int) error { func (s *scenario) theUserSetsTheAddressModeOfUserTo(user, mode string) error { switch mode { case "split": - return s.t.bridge.SetAddressMode(context.Background(), s.t.getUserID(user), vault.SplitMode) + return s.t.bridge.SetAddressMode(context.Background(), s.t.getUserByName(user).getUserID(), vault.SplitMode) case "combined": - return s.t.bridge.SetAddressMode(context.Background(), s.t.getUserID(user), vault.CombinedMode) + return s.t.bridge.SetAddressMode(context.Background(), s.t.getUserByName(user).getUserID(), vault.CombinedMode) default: return fmt.Errorf("unknown address mode %q", mode) @@ -156,7 +156,7 @@ func (s *scenario) bridgeSendsADeauthEventForUser(username string) error { return errors.New("expected deauth event, got none") } - if wantUserID := s.t.getUserID(username); event.UserID != wantUserID { + if wantUserID := s.t.getUserByName(username).getUserID(); event.UserID != wantUserID { return fmt.Errorf("expected deauth event for user %s, got %s", wantUserID, event.UserID) } @@ -169,7 +169,7 @@ func (s *scenario) bridgeSendsAnAddressCreatedEventForUser(username string) erro return errors.New("expected address created event, got none") } - if wantUserID := s.t.getUserID(username); event.UserID != wantUserID { + if wantUserID := s.t.getUserByName(username).getUserID(); event.UserID != wantUserID { return fmt.Errorf("expected address created event for user %s, got %s", wantUserID, event.UserID) } @@ -182,7 +182,7 @@ func (s *scenario) bridgeSendsAnAddressDeletedEventForUser(username string) erro return errors.New("expected address deleted event, got none") } - if wantUserID := s.t.getUserID(username); event.UserID != wantUserID { + if wantUserID := s.t.getUserByName(username).getUserID(); event.UserID != wantUserID { return fmt.Errorf("expected address deleted event for user %s, got %s", wantUserID, event.UserID) } @@ -195,7 +195,7 @@ func (s *scenario) bridgeSendsSyncStartedAndFinishedEventsForUser(username strin return errors.New("expected sync started event, got none") } - if wantUserID := s.t.getUserID(username); startEvent.UserID != wantUserID { + if wantUserID := s.t.getUserByName(username).getUserID(); startEvent.UserID != wantUserID { return fmt.Errorf("expected sync started event for user %s, got %s", wantUserID, startEvent.UserID) } @@ -204,7 +204,7 @@ func (s *scenario) bridgeSendsSyncStartedAndFinishedEventsForUser(username strin return errors.New("expected sync finished event, got none") } - if wantUserID := s.t.getUserID(username); finishEvent.UserID != wantUserID { + if wantUserID := s.t.getUserByName(username).getUserID(); finishEvent.UserID != wantUserID { return fmt.Errorf("expected sync finished event for user %s, got %s", wantUserID, finishEvent.UserID) } diff --git a/tests/ctx_bridge_test.go b/tests/ctx_bridge_test.go index fb43cd60..cb0a2446 100644 --- a/tests/ctx_bridge_test.go +++ b/tests/ctx_bridge_test.go @@ -29,12 +29,14 @@ import ( "runtime" "time" + "github.com/ProtonMail/gluon/imap" "github.com/ProtonMail/gluon/queue" "github.com/ProtonMail/proton-bridge/v3/internal/bridge" "github.com/ProtonMail/proton-bridge/v3/internal/constants" "github.com/ProtonMail/proton-bridge/v3/internal/cookies" "github.com/ProtonMail/proton-bridge/v3/internal/events" frontend "github.com/ProtonMail/proton-bridge/v3/internal/frontend/grpc" + "github.com/ProtonMail/proton-bridge/v3/internal/service" "github.com/ProtonMail/proton-bridge/v3/internal/useragent" "github.com/ProtonMail/proton-bridge/v3/internal/vault" "github.com/sirupsen/logrus" @@ -160,6 +162,7 @@ func (t *testCtx) initBridge() (<-chan events.Event, error) { t.mocks.ProxyCtl, t.mocks.CrashHandler, t.reporter, + imap.DefaultEpochUIDValidityGenerator(), // Logging stuff logIMAP, @@ -260,7 +263,7 @@ func (t *testCtx) initFrontendClient() error { return fmt.Errorf("could not read grpcServerConfig.json: %w", err) } - var cfg frontend.Config + var cfg service.Config if err := json.Unmarshal(b, &cfg); err != nil { return fmt.Errorf("could not unmarshal grpcServerConfig.json: %w", err) diff --git a/tests/ctx_helper_test.go b/tests/ctx_helper_test.go index 510c7309..04c4c51b 100644 --- a/tests/ctx_helper_test.go +++ b/tests/ctx_helper_test.go @@ -20,6 +20,7 @@ package tests import ( "context" "fmt" + "os" "runtime" "github.com/ProtonMail/go-proton-api" @@ -33,6 +34,7 @@ func (t *testCtx) withProton(fn func(*proton.Manager) error) error { proton.WithHostURL(t.api.GetHostURL()), proton.WithTransport(proton.InsecureTransport()), proton.WithAppVersion(t.api.GetAppVersion()), + proton.WithDebug(os.Getenv("FEATURE_API_DEBUG") != ""), ) defer m.Close() @@ -41,7 +43,7 @@ func (t *testCtx) withProton(fn func(*proton.Manager) error) error { // withClient executes the given function with a client that is logged in as the given (known) user. func (t *testCtx) withClient(ctx context.Context, username string, fn func(context.Context, *proton.Client) error) error { - return t.withClientPass(ctx, username, t.getUserPass(t.getUserID(username)), fn) + return t.withClientPass(ctx, username, t.getUserByName(username).getUserPass(), fn) } // withClient executes the given function with a client that is logged in with the given username and password. @@ -106,7 +108,7 @@ func (t *testCtx) withAddrKR( return err } - keyPass, err := salt.SaltForKey([]byte(t.getUserPass(t.getUserID(username))), user.Keys.Primary().ID) + keyPass, err := salt.SaltForKey([]byte(t.getUserByName(username).getUserPass()), user.Keys.Primary().ID) if err != nil { return err } @@ -122,14 +124,19 @@ func (t *testCtx) withAddrKR( func (t *testCtx) createMessages(ctx context.Context, username, addrID string, req []proton.ImportReq) error { return t.withClient(ctx, username, func(ctx context.Context, c *proton.Client) error { return t.withAddrKR(ctx, c, username, addrID, func(ctx context.Context, addrKR *crypto.KeyRing) error { - if _, err := stream.Collect(ctx, c.ImportMessages( + str, err := c.ImportMessages( ctx, addrKR, runtime.NumCPU(), runtime.NumCPU(), req..., - )); err != nil { - return err + ) + if err != nil { + return fmt.Errorf("failed to prepare messages for import: %w", err) + } + + if _, err := stream.Collect(ctx, str); err != nil { + return fmt.Errorf("failed to import messages: %w", err) } return nil diff --git a/tests/ctx_test.go b/tests/ctx_test.go index 0c9db06c..2089ea89 100644 --- a/tests/ctx_test.go +++ b/tests/ctx_test.go @@ -40,12 +40,85 @@ import ( "github.com/emersion/go-imap/client" "github.com/google/uuid" "github.com/sirupsen/logrus" - "golang.org/x/exp/maps" "google.golang.org/grpc" ) var defaultVersion = semver.MustParse("3.0.6") +type testUser struct { + name string // the test user name + userID string // the user's account ID + addresses []*testAddr // the user's addresses + userPass string // the user's account password + bridgePass string // the user's bridge password +} + +func newTestUser(userID, name, userPass string) *testUser { + return &testUser{ + userID: userID, + name: name, + userPass: userPass, + } +} + +func (user *testUser) getName() string { + return user.name +} + +func (user *testUser) getUserID() string { + return user.userID +} + +func (user *testUser) getEmails() []string { + return xslices.Map(user.addresses, func(addr *testAddr) string { + return addr.email + }) +} + +func (user *testUser) getAddrID(email string) string { + for _, addr := range user.addresses { + if addr.email == email { + return addr.addrID + } + } + + panic(fmt.Sprintf("unknown email %q", email)) +} + +func (user *testUser) addAddress(addrID, email string) { + user.addresses = append(user.addresses, newTestAddr(addrID, email)) +} + +func (user *testUser) remAddress(addrID string) { + user.addresses = xslices.Filter(user.addresses, func(addr *testAddr) bool { + return addr.addrID != addrID + }) +} + +func (user *testUser) getUserPass() string { + return user.userPass +} + +func (user *testUser) getBridgePass() string { + return user.bridgePass +} + +func (user *testUser) setBridgePass(pass string) { + user.bridgePass = pass +} + +type testAddr struct { + addrID string // the remote address ID + email string // the test address email +} + +func newTestAddr(addrID, email string) *testAddr { + return &testAddr{ + addrID: addrID, + email: email, + } +} + type testCtx struct { // These are the objects supporting the test. dir string @@ -70,13 +143,11 @@ type testCtx struct { clientConn *grpc.ClientConn clientEventCh *queue.QueuedChannel[*frontend.StreamEvent] - // These maps hold expected userIDByName, their primary addresses and bridge passwords. - userUUIDByName map[string]string - addrUUIDByName map[string]string - userIDByName map[string]string - userAddrByEmail map[string]map[string]string - userPassByID map[string]string - userBridgePassByID map[string][]byte + // These maps hold test objects created during the test. + userByID map[string]*testUser + userUUIDByName map[string]string + addrByID map[string]*testAddr + addrUUIDByName map[string]string // These are the IMAP and SMTP clients used to connect to bridge. imapClients map[string]*imapClient @@ -115,12 +186,10 @@ func newTestCtx(tb testing.TB) *testCtx { events: newEventCollector(), reporter: newReportRecorder(tb), - userUUIDByName: make(map[string]string), - addrUUIDByName: make(map[string]string), - userIDByName: make(map[string]string), - userAddrByEmail: make(map[string]map[string]string), - userPassByID: make(map[string]string), - userBridgePassByID: make(map[string][]byte), + userByID: make(map[string]*testUser), + userUUIDByName: make(map[string]string), + addrByID: make(map[string]*testAddr), + addrUUIDByName: make(map[string]string), imapClients: make(map[string]*imapClient), smtpClients: make(map[string]*smtpClient), @@ -192,62 +261,22 @@ func (t *testCtx) afterStep(st *godog.Step, status godog.StepResultStatus) { logrus.Debugf("Finished step (%v): %s", status, st.Text) } -func (t *testCtx) getName(wantUserID string) string { - for name, userID := range t.userIDByName { - if userID == wantUserID { - return name +func (t *testCtx) addUser(userID, name, userPass string) { + t.userByID[userID] = newTestUser(userID, name, userPass) +} + +func (t *testCtx) getUserByName(name string) *testUser { + for _, user := range t.userByID { + if user.name == name { + return user } } - panic(fmt.Sprintf("unknown user ID %q", wantUserID)) + panic(fmt.Sprintf("user %q not found", name)) } -func (t *testCtx) getUserID(username string) string { - return t.userIDByName[username] -} - -func (t *testCtx) setUserID(username, userID string) { - t.userIDByName[username] = userID -} - -func (t *testCtx) getUserAddrID(userID, email string) string { - return t.userAddrByEmail[userID][email] -} - -func (t *testCtx) getUserAddrs(userID string) []string { - return maps.Keys(t.userAddrByEmail[userID]) -} - -func (t *testCtx) setUserAddr(userID, addrID, email string) { - if _, ok := t.userAddrByEmail[userID]; !ok { - t.userAddrByEmail[userID] = make(map[string]string) - } - - t.userAddrByEmail[userID][email] = addrID -} - -func (t *testCtx) unsetUserAddr(userID, wantAddrID string) { - for email, addrID := range t.userAddrByEmail[userID] { - if addrID == wantAddrID { - delete(t.userAddrByEmail[userID], email) - } - } -} - -func (t *testCtx) getUserPass(userID string) string { - return t.userPassByID[userID] -} - -func (t *testCtx) setUserPass(userID, pass string) { - t.userPassByID[userID] = pass -} - -func (t *testCtx) getUserBridgePass(userID string) string { - return string(t.userBridgePassByID[userID]) -} - -func (t *testCtx) setUserBridgePass(userID string, pass []byte) { - t.userBridgePassByID[userID] = pass +func (t *testCtx) getUserByID(userID string) *testUser { + return t.userByID[userID] } func (t *testCtx) getMBoxID(userID string, name string) string { @@ -256,7 +285,7 @@ func (t *testCtx) getMBoxID(userID string, name string) string { var labelID string - if err := t.withClient(ctx, t.getName(userID), func(ctx context.Context, client *proton.Client) error { + if err := t.withClient(ctx, t.getUserByID(userID).getName(), func(ctx context.Context, client *proton.Client) error { labels, err := client.GetLabels(ctx, proton.LabelTypeLabel, proton.LabelTypeFolder, proton.LabelTypeSystem) if err != nil { panic(err) diff --git a/tests/features/imap/mailbox/create.feature b/tests/features/imap/mailbox/create.feature index ce0e6349..e89ffc7c 100644 --- a/tests/features/imap/mailbox/create.feature +++ b/tests/features/imap/mailbox/create.feature @@ -37,7 +37,7 @@ Feature: IMAP create mailbox Then it succeeds When IMAP client "1" creates "Labels/l3" Then it succeeds - Then IMAP client "1" sees the following mailbox info: + Then IMAP client "1" eventually sees the following mailbox info: | name | | INBOX | | Drafts | @@ -65,7 +65,7 @@ Feature: IMAP create mailbox Then it succeeds When IMAP client "1" creates "Folders/f2/f22" Then it succeeds - Then IMAP client "1" sees the following mailbox info: + Then IMAP client "1" eventually sees the following mailbox info: | name | | INBOX | | Drafts | @@ -89,7 +89,7 @@ Feature: IMAP create mailbox And the user logs in with username "[user:user]" and password "password" And user "[user:user]" finishes syncing And user "[user:user]" connects and authenticates IMAP client "2" - Then IMAP client "2" sees the following mailbox info: + Then IMAP client "2" eventually sees the following mailbox info: | name | | INBOX | | Drafts | @@ -119,7 +119,7 @@ Feature: IMAP create mailbox Then it succeeds When IMAP client "1" creates "Folders/f2/f22" Then it succeeds - Then IMAP client "1" sees the following mailbox info: + Then IMAP client "1" eventually sees the following mailbox info: | name | | INBOX | | Drafts | @@ -143,7 +143,7 @@ Feature: IMAP create mailbox Then it succeeds When IMAP client "1" renames "Folders/f1/f12" to "Folders/f2/f12" Then it succeeds - Then IMAP client "1" sees the following mailbox info: + Then IMAP client "1" eventually sees the following mailbox info: | name | | INBOX | | Drafts | @@ -167,7 +167,7 @@ Feature: IMAP create mailbox And the user logs in with username "[user:user]" and password "password" And user "[user:user]" finishes syncing And user "[user:user]" connects and authenticates IMAP client "2" - Then IMAP client "2" sees the following mailbox info: + Then IMAP client "2" eventually sees the following mailbox info: | name | | INBOX | | Drafts | diff --git a/tests/features/imap/mailbox/hide_all_mail.feature b/tests/features/imap/mailbox/hide_all_mail.feature index ba4e7ba1..9a450e8f 100644 --- a/tests/features/imap/mailbox/hide_all_mail.feature +++ b/tests/features/imap/mailbox/hide_all_mail.feature @@ -7,7 +7,7 @@ Feature: IMAP Hide All Mail And user "[user:user]" connects and authenticates IMAP client "1" Scenario: Hide All Mail Mailbox - Given IMAP client "1" sees the following mailbox info: + Given IMAP client "1" eventually sees the following mailbox info: | name | | INBOX | | Drafts | @@ -20,7 +20,7 @@ Feature: IMAP Hide All Mail | Folders | | Labels | When the user hides All Mail - Then IMAP client "1" sees the following mailbox info: + Then IMAP client "1" eventually sees the following mailbox info: | name | | INBOX | | Drafts | @@ -32,7 +32,7 @@ Feature: IMAP Hide All Mail | Folders | | Labels | When the user shows All Mail - Then IMAP client "1" sees the following mailbox info: + Then IMAP client "1" eventually sees the following mailbox info: | name | | INBOX | | Drafts | diff --git a/tests/features/imap/mailbox/list.feature b/tests/features/imap/mailbox/list.feature index 934409fd..ac939363 100644 --- a/tests/features/imap/mailbox/list.feature +++ b/tests/features/imap/mailbox/list.feature @@ -9,7 +9,7 @@ Feature: IMAP list mailboxes And the user logs in with username "[user:user]" and password "password" And user "[user:user]" finishes syncing And user "[user:user]" connects and authenticates IMAP client "1" - Then IMAP client "1" sees the following mailbox info: + Then IMAP client "1" eventually sees the following mailbox info: | name | | INBOX | | Drafts | @@ -36,4 +36,27 @@ Feature: IMAP list mailboxes Then IMAP client "1" counts 20 mailboxes under "Folders" And IMAP client "1" counts 60 mailboxes under "Labels" Then IMAP client "2" counts 20 mailboxes under "Folders" - And IMAP client "2" counts 60 mailboxes under "Labels" \ No newline at end of file + And IMAP client "2" counts 60 mailboxes under "Labels" + + Scenario: List with scheduled mail + Given there exists an account with username "[user:user]" and password "password" + And the address "[user:user]@[domain]" of account "[user:user]" has the following messages in "Scheduled": + | from | to | subject | unread | + | john.doe@mail.com | [user:user]@[domain] | sch | false | + When bridge starts + And the user logs in with username "[user:user]" and password "password" + And user "[user:user]" finishes syncing + And user "[user:user]" connects and authenticates IMAP client "1" + Then IMAP client "1" eventually sees the following mailbox info: + | name | total | + | INBOX | 0 | + | Drafts | 0 | + | Sent | 0 | + | Starred | 0 | + | Archive | 0 | + | Spam | 0 | + | Trash | 0 | + | All Mail | 1 | + | Folders | 0 | + | Labels | 0 | + | Scheduled | 1 | diff --git a/tests/features/imap/message/copy.feature b/tests/features/imap/message/copy.feature index 3c97b3ef..016fa323 100644 --- a/tests/features/imap/message/copy.feature +++ b/tests/features/imap/message/copy.feature @@ -17,22 +17,22 @@ Feature: IMAP copy messages Scenario: Copy message to label When IMAP client "1" copies the message with subject "foo" from "INBOX" to "Labels/label" And it succeeds - Then IMAP client "1" sees the following messages in "INBOX": + Then IMAP client "1" eventually sees the following messages in "INBOX": | from | to | subject | unread | | john.doe@mail.com | [user:user]@[domain] | foo | false | | jane.doe@mail.com | name@[domain] | bar | true | - And IMAP client "1" sees the following messages in "Labels/label": + And IMAP client "1" eventually sees the following messages in "Labels/label": | from | to | subject | unread | | john.doe@mail.com | [user:user]@[domain] | foo | false | Scenario: Copy all messages to label When IMAP client "1" copies all messages from "INBOX" to "Labels/label" And it succeeds - Then IMAP client "1" sees the following messages in "INBOX": + Then IMAP client "1" eventually sees the following messages in "INBOX": | from | to | subject | unread | | john.doe@mail.com | [user:user]@[domain] | foo | false | | jane.doe@mail.com | name@[domain] | bar | true | - And IMAP client "1" sees the following messages in "Labels/label": + And IMAP client "1" eventually sees the following messages in "Labels/label": | from | to | subject | unread | | john.doe@mail.com | [user:user]@[domain] | foo | false | | jane.doe@mail.com | name@[domain] | bar | true | @@ -43,14 +43,14 @@ Feature: IMAP copy messages Then IMAP client "1" eventually sees the following messages in "INBOX": | from | to | subject | unread | | jane.doe@mail.com | name@[domain] | bar | true | - And IMAP client "1" sees the following messages in "Folders/mbox": + And IMAP client "1" eventually sees the following messages in "Folders/mbox": | from | to | subject | unread | | john.doe@mail.com | [user:user]@[domain] | foo | false | Scenario: Copy all messages to folder does move When IMAP client "1" copies all messages from "INBOX" to "Folders/mbox" And it succeeds - Then IMAP client "1" sees the following messages in "Folders/mbox": + Then IMAP client "1" eventually sees the following messages in "Folders/mbox": | from | to | subject | unread | | john.doe@mail.com | [user:user]@[domain] | foo | false | | jane.doe@mail.com | name@[domain] | bar | true | @@ -66,7 +66,7 @@ Feature: IMAP copy messages And IMAP client "1" eventually sees 0 messages in "Sent" Scenario: Copy message from All mail moves from the original location - Given IMAP client "1" sees the following messages in "INBOX": + Given IMAP client "1" eventually sees the following messages in "INBOX": | from | to | subject | unread | | john.doe@mail.com | [user:user]@[domain] | foo | false | | jane.doe@mail.com | name@[domain] | bar | true | diff --git a/tests/features/imap/message/create.feature b/tests/features/imap/message/create.feature index 733c26c7..367197da 100644 --- a/tests/features/imap/message/create.feature +++ b/tests/features/imap/message/create.feature @@ -12,10 +12,10 @@ Feature: IMAP create messages | from | to | subject | body | | john.doe@email.com | [user:user]@[domain] | foo | bar | Then it succeeds - And IMAP client "1" sees the following messages in "INBOX": + And IMAP client "1" eventually sees the following messages in "INBOX": | from | to | subject | body | | john.doe@email.com | [user:user]@[domain] | foo | bar | - And IMAP client "1" sees the following messages in "All Mail": + And IMAP client "1" eventually sees the following messages in "All Mail": | from | to | subject | body | | john.doe@email.com | [user:user]@[domain] | foo | bar | @@ -37,10 +37,10 @@ Feature: IMAP create messages | from | to | subject | body | | [user:user]@[domain] | john.doe@email.com | foo | bar | Then it succeeds - And IMAP client "1" sees the following messages in "Sent": + And IMAP client "1" eventually sees the following messages in "Sent": | from | to | subject | body | | [user:user]@[domain] | john.doe@email.com | foo | bar | - And IMAP client "1" sees the following messages in "All Mail": + And IMAP client "1" eventually sees the following messages in "All Mail": | from | to | subject | body | | [user:user]@[domain] | john.doe@email.com | foo | bar | @@ -49,10 +49,10 @@ Feature: IMAP create messages | from | to | subject | body | | [alias:alias]@[domain] | john.doe@email.com | foo | bar | Then it succeeds - And IMAP client "1" sees the following messages in "Sent": + And IMAP client "1" eventually sees the following messages in "Sent": | from | to | subject | body | | [alias:alias]@[domain] | john.doe@email.com | foo | bar | - And IMAP client "1" sees the following messages in "All Mail": + And IMAP client "1" eventually sees the following messages in "All Mail": | from | to | subject | body | | [alias:alias]@[domain] | john.doe@email.com | foo | bar | @@ -61,10 +61,10 @@ Feature: IMAP create messages | from | to | subject | body | | john.doe@email.com | john.doe2@[domain] | foo | bar | Then it succeeds - And IMAP client "1" sees the following messages in "INBOX": + And IMAP client "1" eventually sees the following messages in "INBOX": | from | to | subject | body | | john.doe@email.com | john.doe2@[domain] | foo | bar | - And IMAP client "1" sees the following messages in "All Mail": + And IMAP client "1" eventually sees the following messages in "All Mail": | from | to | subject | body | | john.doe@email.com | john.doe2@[domain] | foo | bar | @@ -73,10 +73,10 @@ Feature: IMAP create messages | from | to | subject | body | | john.doe@email.com | john.doe2@[domain] | foo | bar | Then it succeeds - And IMAP client "1" sees the following messages in "Sent": + And IMAP client "1" eventually sees the following messages in "Sent": | from | to | subject | body | | john.doe@email.com | john.doe2@[domain] | foo | bar | - And IMAP client "1" sees the following messages in "All Mail": + And IMAP client "1" eventually sees the following messages in "All Mail": | from | to | subject | body | | john.doe@email.com | john.doe2@[domain] | foo | bar | @@ -85,7 +85,7 @@ Feature: IMAP create messages | from | to | subject | body | | john.doe@email.com | john.doe2@[domain] | foo | bar | And it succeeds - And IMAP client "1" sees the following messages in "Sent": + And IMAP client "1" eventually sees the following messages in "Sent": | from | to | subject | body | | john.doe@email.com | john.doe2@[domain] | foo | bar | And it succeeds @@ -93,7 +93,7 @@ Feature: IMAP create messages | from | to | subject | body | | john.doe@email.com | john.doe2@[domain] | foo | bar | And it succeeds - And IMAP client "1" sees the following messages in "Sent": + And IMAP client "1" eventually sees the following messages in "Sent": | from | to | subject | body | | john.doe@email.com | john.doe2@[domain] | foo | bar | | john.doe@email.com | john.doe2@[domain] | foo | bar | diff --git a/tests/features/imap/message/delete.feature b/tests/features/imap/message/delete.feature index ffe7619d..72fcb288 100644 --- a/tests/features/imap/message/delete.feature +++ b/tests/features/imap/message/delete.feature @@ -6,6 +6,7 @@ Feature: IMAP remove messages from mailbox | mbox | folder | | label | label | And the address "[user:user]@[domain]" of account "[user:user]" has 10 messages in "Folders/mbox" + And the address "[user:user]@[domain]" of account "[user:user]" has 1 messages in "Scheduled" And bridge starts And the user logs in with username "[user:user]" and password "password" And user "[user:user]" finishes syncing @@ -18,14 +19,14 @@ Feature: IMAP remove messages from mailbox Then IMAP client "1" sees that message 2 has the flag "\Deleted" When IMAP client "1" expunges And it succeeds - Then IMAP client "1" sees 9 messages in "Folders/mbox" + Then IMAP client "1" eventually sees 9 messages in "Folders/mbox" Scenario: Mark all messages as deleted and EXPUNGE When IMAP client "1" selects "Folders/mbox" And IMAP client "1" marks all messages as deleted And IMAP client "1" expunges And it succeeds - Then IMAP client "1" sees 0 messages in "Folders/mbox" + Then IMAP client "1" eventually sees 0 messages in "Folders/mbox" Scenario: Mark messages as undeleted and EXPUNGE When IMAP client "1" selects "Folders/mbox" @@ -37,11 +38,18 @@ Feature: IMAP remove messages from mailbox And it succeeds When IMAP client "1" expunges And it succeeds - Then IMAP client "1" sees 2 messages in "Folders/mbox" + Then IMAP client "1" eventually sees 2 messages in "Folders/mbox" Scenario: Not possible to delete from All Mail and expunge does nothing When IMAP client "1" selects "All Mail" And IMAP client "1" marks message 2 as deleted And it succeeds And IMAP client "1" expunges - Then it fails \ No newline at end of file + Then it fails + + Scenario: Not possible to delete from Scheduled and expunge does nothing + When IMAP client "1" selects "Scheduled" + And IMAP client "1" marks message 1 as deleted + Then it succeeds + And IMAP client "1" expunges + Then it fails diff --git a/tests/features/imap/message/delete_from_trash.feature b/tests/features/imap/message/delete_from_trash.feature index 7797f130..fdc5105e 100644 --- a/tests/features/imap/message/delete_from_trash.feature +++ b/tests/features/imap/message/delete_from_trash.feature @@ -20,14 +20,14 @@ Feature: IMAP remove messages from Trash Then it succeeds When IMAP client "1" marks the message with subject "foo" as deleted Then it succeeds - And IMAP client "1" sees 2 messages in "Trash" - And IMAP client "1" sees 2 messages in "All Mail" - And IMAP client "1" sees 1 messages in "Labels/label" + And IMAP client "1" eventually sees 2 messages in "Trash" + And IMAP client "1" eventually sees 2 messages in "All Mail" + And IMAP client "1" eventually sees 1 messages in "Labels/label" When IMAP client "1" expunges Then it succeeds - And IMAP client "1" sees 1 messages in "Trash" - And IMAP client "1" sees 2 messages in "All Mail" - And IMAP client "1" sees 1 messages in "Labels/label" + And IMAP client "1" eventually sees 1 messages in "Trash" + And IMAP client "1" eventually sees 2 messages in "All Mail" + And IMAP client "1" eventually sees 1 messages in "Labels/label" Scenario Outline: Message in Trash only is permanently deleted Given the address "[user:user]@[domain]" of account "[user:user]" has the following messages in "Trash": @@ -41,9 +41,9 @@ Feature: IMAP remove messages from Trash And IMAP client "1" selects "Trash" When IMAP client "1" marks the message with subject "foo" as deleted Then it succeeds - And IMAP client "1" sees 2 messages in "Trash" - And IMAP client "1" sees 2 messages in "All Mail" + And IMAP client "1" eventually sees 2 messages in "Trash" + And IMAP client "1" eventually sees 2 messages in "All Mail" When IMAP client "1" expunges Then it succeeds - And IMAP client "1" sees 1 messages in "Trash" + And IMAP client "1" eventually sees 1 messages in "Trash" And IMAP client "1" eventually sees 1 messages in "All Mail" \ No newline at end of file diff --git a/tests/features/imap/message/drafts.feature b/tests/features/imap/message/drafts.feature index 8ebed248..6f185418 100644 --- a/tests/features/imap/message/drafts.feature +++ b/tests/features/imap/message/drafts.feature @@ -15,7 +15,7 @@ Feature: IMAP Draft messages Then IMAP client "1" eventually sees the following messages in "Drafts": | body | | This is a dra | - And IMAP client "1" sees 1 messages in "Drafts" + And IMAP client "1" eventually sees 1 messages in "Drafts" Scenario: Draft edited locally When IMAP client "1" marks message 1 as deleted @@ -33,7 +33,7 @@ Feature: IMAP Draft messages And IMAP client "1" eventually sees the following messages in "Drafts": | to | subject | body | | someone@example.com | Basic Draft | This is a draft, but longer | - And IMAP client "1" sees 1 messages in "Drafts" + And IMAP client "1" eventually sees 1 messages in "Drafts" Scenario: Draft edited remotely When the following fields were changed in draft 1 for address "[user:user]@[domain]" of account "[user:user]": @@ -42,12 +42,12 @@ Feature: IMAP Draft messages Then IMAP client "1" eventually sees the following messages in "Drafts": | to | subject | body | | someone@example.com | Basic Draft | This is a draft body, but longer | - And IMAP client "1" sees 1 messages in "Drafts" + And IMAP client "1" eventually sees 1 messages in "Drafts" Scenario: Draft moved to trash remotely When draft 1 for address "[user:user]@[domain]" of account "[user:user] was moved to trash Then IMAP client "1" eventually sees the following messages in "Trash": | body | | This is a dra | - And IMAP client "1" sees 0 messages in "Drafts" + And IMAP client "1" eventually sees 0 messages in "Drafts" diff --git a/tests/features/imap/message/fetch.feature b/tests/features/imap/message/fetch.feature index 7c4d6fcc..f72461b4 100644 --- a/tests/features/imap/message/fetch.feature +++ b/tests/features/imap/message/fetch.feature @@ -13,7 +13,7 @@ Feature: IMAP Fetch And user "[user:user]" connects and authenticates IMAP client "1" Scenario: Fetch very old message - Given IMAP client "1" sees the following messages in "INBOX": + Given IMAP client "1" eventually sees the following messages in "INBOX": | from | to | subject | date | | john.doe@mail.com | [user:user]@[domain] | foo | 13 Aug 82 00:00 +0000 | Then IMAP client "1" sees header "X-Original-Date: Sun, 13 Jul 1969 00:00:00 +0000" in message with subject "foo" in "INBOX" @@ -21,6 +21,6 @@ Feature: IMAP Fetch Scenario: Fetch from deleted cache When the user deletes the gluon cache - Then IMAP client "1" sees the following messages in "INBOX": + Then IMAP client "1" eventually sees the following messages in "INBOX": | from | to | subject | date | | john.doe@mail.com | [user:user]@[domain] | foo | 13 Aug 82 00:00 +0000 | diff --git a/tests/features/imap/message/import.feature b/tests/features/imap/message/import.feature index a3c36f0a..b70ad4c7 100644 --- a/tests/features/imap/message/import.feature +++ b/tests/features/imap/message/import.feature @@ -104,7 +104,7 @@ Feature: IMAP import messages And IMAP client "1" eventually sees the following messages in "Sent": | from | to | subject | body | | foo@example.com | bridgetest@pm.test | Hello | Hello | - And IMAP client "1" sees 0 messages in "Inbox" + And IMAP client "1" eventually sees 0 messages in "Inbox" Scenario: Import non-received message to Inbox When IMAP client "1" appends the following message to "Inbox": @@ -119,7 +119,7 @@ Feature: IMAP import messages And IMAP client "1" eventually sees the following messages in "INBOX": | from | to | subject | body | | foo@example.com | bridgetest@pm.test | Hello | Hello | - And IMAP client "1" sees 0 messages in "Sent" + And IMAP client "1" eventually sees 0 messages in "Sent" Scenario: Import non-received message to Sent When IMAP client "1" appends the following message to "Sent": @@ -134,7 +134,7 @@ Feature: IMAP import messages And IMAP client "1" eventually sees the following messages in "Sent": | from | to | subject | body | | foo@example.com | bridgetest@pm.test | Hello | Hello | - And IMAP client "1" sees 0 messages in "Inbox" + And IMAP client "1" eventually sees 0 messages in "Inbox" Scenario Outline: Import message without sender to When IMAP client "1" appends the following message to "": diff --git a/tests/features/imap/message/move.feature b/tests/features/imap/message/move.feature index 272498d8..33235de5 100644 --- a/tests/features/imap/message/move.feature +++ b/tests/features/imap/message/move.feature @@ -16,6 +16,9 @@ Feature: IMAP move messages And the address "[user:user]@[domain]" of account "[user:user]" has the following messages in "Sent": | from | to | subject | unread | | john.doe@mail.com | [user:user]@[domain] | bax | false | + And the address "[user:user]@[domain]" of account "[user:user]" has the following messages in "Scheduled": + | from | to | subject | unread | + | john.doe@mail.com | [user:user]@[domain] | sch | false | And bridge starts And the user logs in with username "[user:user]" and password "password" And user "[user:user]" finishes syncing @@ -24,11 +27,11 @@ Feature: IMAP move messages Scenario: Move message from folder to label (keeps in folder) When IMAP client "1" moves the message with subject "foo" from "INBOX" to "Labels/label" And it succeeds - And IMAP client "1" sees the following messages in "INBOX": + And IMAP client "1" eventually sees the following messages in "INBOX": | from | to | subject | unread | | john.doe@mail.com | [user:user]@[domain] | foo | false | | jane.doe@mail.com | name@[domain] | bar | true | - And IMAP client "1" sees the following messages in "Labels/label": + And IMAP client "1" eventually sees the following messages in "Labels/label": | from | to | subject | unread | | john.doe@mail.com | [user:user]@[domain] | foo | false | @@ -41,63 +44,95 @@ Feature: IMAP move messages And IMAP client "target" selects "Labels/label" And IMAP clients "source" and "target" move message with subject "foo" of "[user:user]" to "Labels/label" by APPEND DELETE EXPUNGE And it succeeds - Then IMAP client "source" sees the following messages in "INBOX": + Then IMAP client "source" eventually sees the following messages in "INBOX": | from | to | subject | unread | | jane.doe@mail.com | name@[domain] | bar | true | - And IMAP client "target" sees the following messages in "Labels/label": + And IMAP client "target" eventually sees the following messages in "Labels/label": | from | to | subject | unread | | john.doe@mail.com | [user:user]@[domain] | foo | false | Scenario: Move message from label to folder When IMAP client "1" moves the message with subject "baz" from "Labels/label2" to "Folders/mbox" And it succeeds - And IMAP client "1" sees the following messages in "Folders/mbox": + And IMAP client "1" eventually sees the following messages in "Folders/mbox": | from | to | subject | unread | | john.doe@mail.com | [user:user]@[domain] | baz | false | - And IMAP client "1" sees 0 messages in "Labels/label2" + And IMAP client "1" eventually sees 0 messages in "Labels/label2" Scenario: Move message from label to label When IMAP client "1" moves the message with subject "baz" from "Labels/label2" to "Labels/label" And it succeeds - And IMAP client "1" sees the following messages in "Labels/label": + And IMAP client "1" eventually sees the following messages in "Labels/label": | from | to | subject | unread | | john.doe@mail.com | [user:user]@[domain] | baz | false | - And IMAP client "1" sees 0 messages in "Labels/label2" + And IMAP client "1" eventually sees 0 messages in "Labels/label2" Scenario: Move message from system label to system label When IMAP client "1" moves the message with subject "foo" from "INBOX" to "Trash" And it succeeds - And IMAP client "1" sees the following messages in "INBOX": + And IMAP client "1" eventually sees the following messages in "INBOX": | from | to | subject | unread | | jane.doe@mail.com | name@[domain] | bar | true | - And IMAP client "1" sees the following messages in "Trash": + And IMAP client "1" eventually sees the following messages in "Trash": | from | to | subject | unread | | john.doe@mail.com | [user:user]@[domain] | foo | false | Scenario: Move message from folder to system label When IMAP client "1" moves the message with subject "baz" from "Labels/label2" to "Folders/mbox" And it succeeds - And IMAP client "1" sees the following messages in "Folders/mbox": + And IMAP client "1" eventually sees the following messages in "Folders/mbox": | from | to | subject | unread | | john.doe@mail.com | [user:user]@[domain] | baz | false | When IMAP client "1" moves the message with subject "baz" from "Folders/mbox" to "Trash" And it succeeds - And IMAP client "1" sees 0 messages in "Folders/mbox" - And IMAP client "1" sees the following messages in "Trash": + And IMAP client "1" eventually sees 0 messages in "Folders/mbox" + And IMAP client "1" eventually sees the following messages in "Trash": + | from | to | subject | unread | + | john.doe@mail.com | [user:user]@[domain] | baz | false | + + Scenario: Move message from system label to system label + When IMAP client "1" moves the message with subject "foo" from "INBOX" to "Trash" + And it succeeds + And IMAP client "1" eventually sees the following messages in "INBOX": + | from | to | subject | unread | + | jane.doe@mail.com | name@[domain] | bar | true | + And IMAP client "1" eventually sees the following messages in "Trash": + | from | to | subject | unread | + | john.doe@mail.com | [user:user]@[domain] | foo | false | + + Scenario: Move message from folder to system label + When IMAP client "1" moves the message with subject "baz" from "Labels/label2" to "Folders/mbox" + And it succeeds + And IMAP client "1" eventually sees the following messages in "Folders/mbox": + | from | to | subject | unread | + | john.doe@mail.com | [user:user]@[domain] | baz | false | + When IMAP client "1" moves the message with subject "baz" from "Folders/mbox" to "Trash" + And it succeeds + And IMAP client "1" eventually sees 0 messages in "Folders/mbox" + And IMAP client "1" eventually sees the following messages in "Trash": | from | to | subject | unread | | john.doe@mail.com | [user:user]@[domain] | baz | false | Scenario: Move message from All Mail is not possible When IMAP client "1" moves the message with subject "baz" from "All Mail" to "Folders/folder" Then it fails - And IMAP client "1" sees the following messages in "All Mail": + And IMAP client "1" eventually sees the following messages in "All Mail": | from | to | subject | unread | | john.doe@mail.com | [user:user]@[domain] | foo | false | | jane.doe@mail.com | name@[domain] | bar | true | | john.doe@mail.com | [user:user]@[domain] | baz | false | | john.doe@mail.com | [user:user]@[domain] | bax | false | + | john.doe@mail.com | [user:user]@[domain] | sch | false | - Scenario: Move message from Inbox to Sent is not possible + Scenario: Move message from Scheduled is not possible + Given test skips reporter checks + When IMAP client "1" moves the message with subject "sch" from "Scheduled" to "Inbox" + Then it fails + And IMAP client "1" eventually sees the following messages in "Scheduled": + | from | to | subject | unread | + | john.doe@mail.com | [user:user]@[domain] | sch | false | + + Scenario: Move message from Inbox to Sent is not possible Given test skips reporter checks When IMAP client "1" moves the message with subject "bar" from "Inbox" to "Sent" Then it fails @@ -105,4 +140,4 @@ Feature: IMAP move messages Scenario: Move message from Sent to Inbox is not possible Given test skips reporter checks When IMAP client "1" moves the message with subject "bax" from "Sent" to "Inbox" - Then it fails \ No newline at end of file + Then it fails diff --git a/tests/features/imap/message/move_without_support.feature b/tests/features/imap/message/move_without_support.feature index bdf583b7..ca81095e 100644 --- a/tests/features/imap/message/move_without_support.feature +++ b/tests/features/imap/message/move_without_support.feature @@ -34,8 +34,8 @@ Feature: IMAP move messages by append and delete (without MOVE support, e.g., Ou And IMAP client "source" selects "" And IMAP client "target" selects "" When IMAP clients "source" and "target" move message with subject "subj2" of "[user:user]" to "" by - And IMAP client "source" sees 1 messages in "" - And IMAP client "source" sees the following messages in "": + And IMAP client "source" eventually sees 1 messages in "" + And IMAP client "source" eventually sees the following messages in "": | from | to | subject | | sndr1@[domain] | rcvr1@[domain] | subj1 | And IMAP client "target" eventually sees 1 messages in "" diff --git a/tests/features/imap/migration.feature b/tests/features/imap/migration.feature index 136257f1..4d6a11a9 100644 --- a/tests/features/imap/migration.feature +++ b/tests/features/imap/migration.feature @@ -17,7 +17,7 @@ Feature: Bridge can fully sync an account Scenario: The user changes the gluon path When the user changes the gluon path And user "[user:user]" connects and authenticates IMAP client "2" - Then IMAP client "2" sees the following messages in "INBOX": + Then IMAP client "2" eventually sees the following messages in "INBOX": | from | to | subject | unread | | john.doe@mail.com | [user:user]@[domain] | foo | false | | jane.doe@mail.com | name@[domain] | bar | true | diff --git a/tests/features/smtp/send/bcc.feature b/tests/features/smtp/send/bcc.feature index ec8d41e6..a41f60da 100644 --- a/tests/features/smtp/send/bcc.feature +++ b/tests/features/smtp/send/bcc.feature @@ -45,6 +45,7 @@ Feature: SMTP with bcc """ + @long-black Scenario: Send message only to bcc When SMTP client "1" sends the following message from "[user:user]@[domain]" to "[user:bcc]@[domain]": """ diff --git a/tests/features/smtp/send/embedded_message.feature b/tests/features/smtp/send/embedded_message.feature index 38154a39..3ab685c9 100644 --- a/tests/features/smtp/send/embedded_message.feature +++ b/tests/features/smtp/send/embedded_message.feature @@ -7,6 +7,7 @@ Feature: SMTP sending embedded message And the user logs in with username "[user:to]" and password "password" And user "[user:user]" connects and authenticates SMTP client "1" + @long-black Scenario: Send it When SMTP client "1" sends the following message from "[user:user]@[domain]" to "[user:to]@[domain]": """ diff --git a/tests/features/smtp/send/one_account_to_another.feature b/tests/features/smtp/send/one_account_to_another.feature index 08d1a53a..62e20587 100644 --- a/tests/features/smtp/send/one_account_to_another.feature +++ b/tests/features/smtp/send/one_account_to_another.feature @@ -6,6 +6,8 @@ Feature: SMTP sending two messages And the user logs in with username "[user:user]" and password "password" And the user logs in with username "[user:recp]" and password "password" + + @long-black Scenario: Send from one account to the other When user "[user:user]" connects and authenticates SMTP client "1" And SMTP client "1" sends the following message from "[user:user]@[domain]" to "[user:recp]@[domain]": @@ -60,6 +62,8 @@ Feature: SMTP sending two messages | from | to | subject | body | | [user:user]@[domain] | [user:recp]@[domain] | One account to the other | hello | + + @long-black Scenario: Send from one account to the other with attachments When user "[user:user]" connects and authenticates SMTP client "1" And SMTP client "1" sends the following message from "[user:user]@[domain]" to "[user:recp]@[domain]": diff --git a/tests/features/smtp/send/same_message.feature b/tests/features/smtp/send/same_message.feature index dddb810b..394b28d3 100644 --- a/tests/features/smtp/send/same_message.feature +++ b/tests/features/smtp/send/same_message.feature @@ -16,6 +16,7 @@ Feature: SMTP sending the same message twice """ And it succeeds + @long-black Scenario: The exact same message is not sent twice When SMTP client "1" sends the following message from "[user:user]@[domain]" to "[user:to]@[domain]": """ @@ -36,6 +37,7 @@ Feature: SMTP sending the same message twice | [user:user]@[domain] | [user:to]@[domain] | Hello | World | + @long-black Scenario: Slight change means different message and is sent twice When SMTP client "1" sends the following message from "[user:user]@[domain]" to "[user:to]@[domain]": """ diff --git a/tests/features/smtp/send/send_reply.feature b/tests/features/smtp/send/send_reply.feature new file mode 100644 index 00000000..47f9acf4 --- /dev/null +++ b/tests/features/smtp/send/send_reply.feature @@ -0,0 +1,153 @@ +Feature: SMTP send reply + + Background: + Given there exists an account with username "[user:user1]" and password "password" + And there exists an account with username "[user:user2]" and password "password" + And bridge starts + And the user logs in with username "[user:user1]" and password "password" + And user "[user:user1]" connects and authenticates SMTP client "1" + And user "[user:user1]" connects and authenticates IMAP client "1" + And user "[user:user1]" finishes syncing + + @long-black + Scenario: Reply with In-Reply-To but no References + # User1 send the initial message. + When SMTP client "1" sends the following message from "[user:user1]@[domain]" to "[user:user2]@[domain]": + """ + From: Bridge Test <[user:user1]@[domain]> + To: Internal Bridge <[user:user2]@[domain]> + Subject: Please Reply + Message-ID: + + hello + + """ + Then it succeeds + Then IMAP client "1" eventually sees the following messages in "Sent": + | from | to | subject | message-id | + | [user:user1]@[domain] | [user:user2]@[domain] | Please Reply | | + # login user2. + And the user logs in with username "[user:user2]" and password "password" + And user "[user:user2]" connects and authenticates IMAP client "2" + And user "[user:user2]" connects and authenticates SMTP client "2" + And user "[user:user2]" finishes syncing + # User2 receive the message. + Then IMAP client "2" eventually sees the following messages in "INBOX": + | from | subject | message-id | + | [user:user1]@[domain] | Please Reply | | + # User2 reply to it. + When SMTP client "2" sends the following message from "[user:user2]@[domain]" to "[user:user1]@[domain]": + """ + From: Internal Bridge <[user:user2]@[domain]> + To: Bridge Test <[user:user1]@[domain]> + Content-Type: text/plain + Subject: FW - Please Reply + In-Reply-To: + + Heya + + """ + Then it succeeds + Then IMAP client "2" eventually sees the following messages in "Sent": + | from | to | subject | in-reply-to | references | + | [user:user2]@[domain] | [user:user1]@[domain] | FW - Please Reply | | | + # User1 receive the reply.| + And IMAP client "1" eventually sees the following messages in "INBOX": + | from | subject | body | in-reply-to | references | + | [user:user2]@[domain] | FW - Please Reply | Heya | | | + + @long-black + Scenario: Reply with References but no In-Reply-To + # User1 send the initial message. + When SMTP client "1" sends the following message from "[user:user1]@[domain]" to "[user:user2]@[domain]": + """ + From: Bridge Test <[user:user1]@[domain]> + To: Internal Bridge <[user:user2]@[domain]> + Subject: Please Reply + Message-ID: + + hello + + """ + Then it succeeds + Then IMAP client "1" eventually sees the following messages in "Sent": + | from | to | subject | message-id | + | [user:user1]@[domain] | [user:user2]@[domain] | Please Reply | | + # login user2. + And the user logs in with username "[user:user2]" and password "password" + And user "[user:user2]" connects and authenticates IMAP client "2" + And user "[user:user2]" connects and authenticates SMTP client "2" + And user "[user:user2]" finishes syncing + # User2 receive the message. + Then IMAP client "2" eventually sees the following messages in "INBOX": + | from | subject | message-id | + | [user:user1]@[domain] | Please Reply | | + # User2 reply to it. + When SMTP client "2" sends the following message from "[user:user2]@[domain]" to "[user:user1]@[domain]": + """ + From: Internal Bridge <[user:user2]@[domain]> + To: Bridge Test <[user:user1]@[domain]> + Content-Type: text/plain + Subject: FW - Please Reply + References: + + Heya + + """ + Then it succeeds + Then IMAP client "2" eventually sees the following messages in "Sent": + | from | to | subject | in-reply-to | references | + | [user:user2]@[domain] | [user:user1]@[domain] | FW - Please Reply | | | + # User1 receive the reply.| + And IMAP client "1" eventually sees the following messages in "INBOX": + | from | subject | body | in-reply-to | references | + | [user:user2]@[domain] | FW - Please Reply | Heya | | | + + + @long-black + Scenario: Reply with both References and In-Reply-To + # User1 send the initial message. + When SMTP client "1" sends the following message from "[user:user1]@[domain]" to "[user:user2]@[domain]": + """ + From: Bridge Test <[user:user1]@[domain]> + To: Internal Bridge <[user:user2]@[domain]> + Subject: Please Reply + Message-ID: + + hello + + """ + Then it succeeds + Then IMAP client "1" eventually sees the following messages in "Sent": + | from | to | subject | message-id | + | [user:user1]@[domain] | [user:user2]@[domain] | Please Reply | | + # login user2. + And the user logs in with username "[user:user2]" and password "password" + And user "[user:user2]" connects and authenticates IMAP client "2" + And user "[user:user2]" connects and authenticates SMTP client "2" + And user "[user:user2]" finishes syncing + # User2 receive the message. + Then IMAP client "2" eventually sees the following messages in "INBOX": + | from | subject | message-id | + | [user:user1]@[domain] | Please Reply | | + # User2 reply to it. + When SMTP client "2" sends the following message from "[user:user2]@[domain]" to "[user:user1]@[domain]": + """ + From: Internal Bridge <[user:user2]@[domain]> + To: Bridge Test <[user:user1]@[domain]> + Content-Type: text/plain + Subject: FW - Please Reply + In-Reply-To: + References: + + Heya + + """ + Then it succeeds + Then IMAP client "2" eventually sees the following messages in "Sent": + | from | to | subject | in-reply-to | references | + | [user:user2]@[domain] | [user:user1]@[domain] | FW - Please Reply | | | + # User1 receive the reply.| + And IMAP client "1" eventually sees the following messages in "INBOX": + | from | subject | body | in-reply-to | references | + | [user:user2]@[domain] | FW - Please Reply | Heya | | | \ No newline at end of file diff --git a/tests/features/user/addressmode.feature b/tests/features/user/addressmode.feature index d447a4c1..714e2e3c 100644 --- a/tests/features/user/addressmode.feature +++ b/tests/features/user/addressmode.feature @@ -20,30 +20,30 @@ Feature: Address mode Scenario: The user is in combined mode When user "[user:user]" connects and authenticates IMAP client "1" with address "[user:user]@[domain]" - Then IMAP client "1" sees the following messages in "Folders/one": + Then IMAP client "1" eventually sees the following messages in "Folders/one": | from | to | subject | unread | | a@[domain] | a@[domain] | one | true | | b@[domain] | b@[domain] | two | false | - And IMAP client "1" sees the following messages in "Folders/two": + And IMAP client "1" eventually sees the following messages in "Folders/two": | from | to | subject | unread | | c@[domain] | c@[domain] | three | true | | d@[domain] | d@[domain] | four | false | - And IMAP client "1" sees the following messages in "All Mail": + And IMAP client "1" eventually sees the following messages in "All Mail": | from | to | subject | unread | | a@[domain] | a@[domain] | one | true | | b@[domain] | b@[domain] | two | false | | c@[domain] | c@[domain] | three | true | | d@[domain] | d@[domain] | four | false | When user "[user:user]" connects and authenticates IMAP client "2" with address "[alias:alias]@[domain]" - Then IMAP client "2" sees the following messages in "Folders/one": + Then IMAP client "2" eventually sees the following messages in "Folders/one": | from | to | subject | unread | | a@[domain] | a@[domain] | one | true | | b@[domain] | b@[domain] | two | false | - And IMAP client "2" sees the following messages in "Folders/two": + And IMAP client "2" eventually sees the following messages in "Folders/two": | from | to | subject | unread | | c@[domain] | c@[domain] | three | true | | d@[domain] | d@[domain] | four | false | - And IMAP client "2" sees the following messages in "All Mail": + And IMAP client "2" eventually sees the following messages in "All Mail": | from | to | subject | unread | | a@[domain] | a@[domain] | one | true | | b@[domain] | b@[domain] | two | false | @@ -54,22 +54,22 @@ Feature: Address mode Given the user sets the address mode of user "[user:user]" to "split" And user "[user:user]" finishes syncing When user "[user:user]" connects and authenticates IMAP client "1" with address "[user:user]@[domain]" - Then IMAP client "1" sees the following messages in "Folders/one": + Then IMAP client "1" eventually sees the following messages in "Folders/one": | from | to | subject | unread | | a@[domain] | a@[domain] | one | true | | b@[domain] | b@[domain] | two | false | - And IMAP client "1" sees 0 messages in "Folders/two" - And IMAP client "1" sees the following messages in "All Mail": + And IMAP client "1" eventually sees 0 messages in "Folders/two" + And IMAP client "1" eventually sees the following messages in "All Mail": | from | to | subject | unread | | a@[domain] | a@[domain] | one | true | | b@[domain] | b@[domain] | two | false | When user "[user:user]" connects and authenticates IMAP client "2" with address "[alias:alias]@[domain]" - Then IMAP client "2" sees 0 messages in "Folders/one" - And IMAP client "2" sees the following messages in "Folders/two": + Then IMAP client "2" eventually sees 0 messages in "Folders/one" + And IMAP client "2" eventually sees the following messages in "Folders/two": | from | to | subject | unread | | c@[domain] | c@[domain] | three | true | | d@[domain] | d@[domain] | four | false | - And IMAP client "2" sees the following messages in "All Mail": + And IMAP client "2" eventually sees the following messages in "All Mail": | from | to | subject | unread | | c@[domain] | c@[domain] | three | true | | d@[domain] | d@[domain] | four | false | @@ -80,14 +80,14 @@ Feature: Address mode And the user sets the address mode of user "[user:user]" to "combined" And user "[user:user]" finishes syncing When user "[user:user]" connects and authenticates IMAP client "1" with address "[user:user]@[domain]" - Then IMAP client "1" sees the following messages in "All Mail": + Then IMAP client "1" eventually sees the following messages in "All Mail": | from | to | subject | unread | | a@[domain] | a@[domain] | one | true | | b@[domain] | b@[domain] | two | false | | c@[domain] | c@[domain] | three | true | | d@[domain] | d@[domain] | four | false | When user "[user:user]" connects and authenticates IMAP client "2" with address "[alias:alias]@[domain]" - Then IMAP client "2" sees the following messages in "All Mail": + Then IMAP client "2" eventually sees the following messages in "All Mail": | from | to | subject | unread | | a@[domain] | a@[domain] | one | true | | b@[domain] | b@[domain] | two | false | @@ -96,14 +96,14 @@ Feature: Address mode Scenario: The user adds an address while in combined mode When user "[user:user]" connects and authenticates IMAP client "1" with address "[user:user]@[domain]" - Then IMAP client "1" sees the following messages in "All Mail": + Then IMAP client "1" eventually sees the following messages in "All Mail": | from | to | subject | unread | | a@[domain] | a@[domain] | one | true | | b@[domain] | b@[domain] | two | false | | c@[domain] | c@[domain] | three | true | | d@[domain] | d@[domain] | four | false | When user "[user:user]" connects and authenticates IMAP client "2" with address "[alias:alias]@[domain]" - Then IMAP client "2" sees the following messages in "All Mail": + Then IMAP client "2" eventually sees the following messages in "All Mail": | from | to | subject | unread | | a@[domain] | a@[domain] | one | true | | b@[domain] | b@[domain] | two | false | @@ -112,7 +112,7 @@ Feature: Address mode Given the account "[user:user]" has additional address "other@[domain]" And bridge sends an address created event for user "[user:user]" When user "[user:user]" connects and authenticates IMAP client "3" with address "other@[domain]" - Then IMAP client "3" sees the following messages in "All Mail": + Then IMAP client "3" eventually sees the following messages in "All Mail": | from | to | subject | unread | | a@[domain] | a@[domain] | one | true | | b@[domain] | b@[domain] | two | false | @@ -123,12 +123,12 @@ Feature: Address mode Given the user sets the address mode of user "[user:user]" to "split" And user "[user:user]" finishes syncing When user "[user:user]" connects and authenticates IMAP client "1" with address "[user:user]@[domain]" - And IMAP client "1" sees the following messages in "All Mail": + And IMAP client "1" eventually sees the following messages in "All Mail": | from | to | subject | unread | | a@[domain] | a@[domain] | one | true | | b@[domain] | b@[domain] | two | false | When user "[user:user]" connects and authenticates IMAP client "2" with address "[alias:alias]@[domain]" - And IMAP client "2" sees the following messages in "All Mail": + And IMAP client "2" eventually sees the following messages in "All Mail": | from | to | subject | unread | | c@[domain] | c@[domain] | three | true | | d@[domain] | d@[domain] | four | false | @@ -139,14 +139,14 @@ Feature: Address mode Scenario: The user deletes an address while in combined mode When user "[user:user]" connects and authenticates IMAP client "1" with address "[user:user]@[domain]" - Then IMAP client "1" sees the following messages in "All Mail": + Then IMAP client "1" eventually sees the following messages in "All Mail": | from | to | subject | unread | | a@[domain] | a@[domain] | one | true | | b@[domain] | b@[domain] | two | false | | c@[domain] | c@[domain] | three | true | | d@[domain] | d@[domain] | four | false | When user "[user:user]" connects and authenticates IMAP client "2" with address "[alias:alias]@[domain]" - Then IMAP client "2" sees the following messages in "All Mail": + Then IMAP client "2" eventually sees the following messages in "All Mail": | from | to | subject | unread | | a@[domain] | a@[domain] | one | true | | b@[domain] | b@[domain] | two | false | @@ -161,12 +161,12 @@ Feature: Address mode Given the user sets the address mode of user "[user:user]" to "split" And user "[user:user]" finishes syncing When user "[user:user]" connects and authenticates IMAP client "1" with address "[user:user]@[domain]" - And IMAP client "1" sees the following messages in "All Mail": + And IMAP client "1" eventually sees the following messages in "All Mail": | from | to | subject | unread | | a@[domain] | a@[domain] | one | true | | b@[domain] | b@[domain] | two | false | When user "[user:user]" connects and authenticates IMAP client "2" with address "[alias:alias]@[domain]" - And IMAP client "2" sees the following messages in "All Mail": + And IMAP client "2" eventually sees the following messages in "All Mail": | from | to | subject | unread | | c@[domain] | c@[domain] | three | true | | d@[domain] | d@[domain] | four | false | diff --git a/tests/features/user/login.feature b/tests/features/user/login.feature index 385a7984..1e512431 100644 --- a/tests/features/user/login.feature +++ b/tests/features/user/login.feature @@ -7,15 +7,15 @@ Feature: A user can login Scenario: Login to account When the user logs in with username "[user:user]" and password "password" - Then user "[user:user]" is listed and connected + Then user "[user:user]" is eventually listed and connected Scenario: Login to account with wrong password When the user logs in with username "[user:user]" and password "wrong" Then user "[user:user]" is not listed Scenario: Login to nonexistent account - When the user logs in with username "[user:other]" and password "unknown" - Then user "[user:other]" is not listed + When the user logs in with username "nonexistent" and password "unknown" + Then user "nonexistent" is not listed Scenario: Login to account without internet Given the internet is turned off @@ -24,11 +24,11 @@ Feature: A user can login Scenario: Login to account with caps When the user logs in with username "[user:MixedCaps]" and password "password" - Then user "[user:MixedCaps]" is listed and connected + Then user "[user:MixedCaps]" is eventually listed and connected Scenario: Login to account with disabled primary When the user logs in with username "[user:disabled]" and password "password" - Then user "[user:disabled]" is listed and connected + Then user "[user:disabled]" is eventually listed and connected Scenario: Login to account without internet but the connection is later restored When the user logs in with username "[user:user]" and password "password" @@ -42,5 +42,5 @@ Feature: A user can login Given there exists an account with username "[user:additional]" and password "password" When the user logs in with username "[user:user]" and password "password" And the user logs in with username "[user:additional]" and password "password" - Then user "[user:user]" is listed and connected - And user "[user:additional]" is listed and connected \ No newline at end of file + Then user "[user:user]" is eventually listed and connected + And user "[user:additional]" is eventually listed and connected \ No newline at end of file diff --git a/tests/features/user/relogin.feature b/tests/features/user/relogin.feature index 2da7b437..021a6696 100644 --- a/tests/features/user/relogin.feature +++ b/tests/features/user/relogin.feature @@ -8,7 +8,7 @@ Feature: A logged out user can login again When user "[user:user]" logs out And bridge restarts And the user logs in with username "[user:user]" and password "password" - Then user "[user:user]" is listed and connected + Then user "[user:user]" is eventually listed and connected Scenario: Cannot login to removed account When user "[user:user]" is deleted diff --git a/tests/features/user/sync.feature b/tests/features/user/sync.feature index 5f0fbf91..260eea3d 100644 --- a/tests/features/user/sync.feature +++ b/tests/features/user/sync.feature @@ -21,7 +21,7 @@ Feature: Bridge can fully sync an account Then bridge sends sync started and finished events for user "[user:user]" When bridge restarts And user "[user:user]" connects and authenticates IMAP client "1" - Then IMAP client "1" sees the following mailbox info: + Then IMAP client "1" eventually sees the following mailbox info: | name | total | unread | | INBOX | 0 | 0 | | Drafts | 0 | 0 | diff --git a/tests/imap_test.go b/tests/imap_test.go index 2f2cf448..8cae5707 100644 --- a/tests/imap_test.go +++ b/tests/imap_test.go @@ -38,35 +38,35 @@ import ( ) func (s *scenario) userConnectsIMAPClient(username, clientID string) error { - return s.t.newIMAPClient(s.t.getUserID(username), clientID) + return s.t.newIMAPClient(s.t.getUserByName(username).getUserID(), clientID) } func (s *scenario) userConnectsIMAPClientOnPort(username, clientID string, port int) error { - return s.t.newIMAPClientOnPort(s.t.getUserID(username), clientID, port) + return s.t.newIMAPClientOnPort(s.t.getUserByName(username).getUserID(), clientID, port) } func (s *scenario) userConnectsAndAuthenticatesIMAPClient(username, clientID string) error { - return s.userConnectsAndAuthenticatesIMAPClientWithAddress(username, clientID, s.t.getUserAddrs(s.t.getUserID(username))[0]) + return s.userConnectsAndAuthenticatesIMAPClientWithAddress(username, clientID, s.t.getUserByName(username).getEmails()[0]) } func (s *scenario) userConnectsAndAuthenticatesIMAPClientWithAddress(username, clientID, address string) error { - if err := s.t.newIMAPClient(s.t.getUserID(username), clientID); err != nil { + if err := s.t.newIMAPClient(s.t.getUserByName(username).getUserID(), clientID); err != nil { return err } userID, client := s.t.getIMAPClient(clientID) - return client.Login(address, s.t.getUserBridgePass(userID)) + return client.Login(address, s.t.getUserByID(userID).getBridgePass()) } func (s *scenario) userConnectsAndCanNotAuthenticateIMAPClientWithAddress(username, clientID, address string) error { - if err := s.t.newIMAPClient(s.t.getUserID(username), clientID); err != nil { + if err := s.t.newIMAPClient(s.t.getUserByName(username).getUserID(), clientID); err != nil { return err } userID, client := s.t.getIMAPClient(clientID) - if err := client.Login(address, s.t.getUserBridgePass(userID)); err == nil { + if err := client.Login(address, s.t.getUserByID(userID).getBridgePass()); err == nil { return fmt.Errorf("expected error, got nil") } @@ -76,19 +76,19 @@ func (s *scenario) userConnectsAndCanNotAuthenticateIMAPClientWithAddress(userna func (s *scenario) imapClientCanAuthenticate(clientID string) error { userID, client := s.t.getIMAPClient(clientID) - return client.Login(s.t.getUserAddrs(userID)[0], s.t.getUserBridgePass(userID)) + return client.Login(s.t.getUserByID(userID).getEmails()[0], s.t.getUserByID(userID).getBridgePass()) } func (s *scenario) imapClientCanAuthenticateWithAddress(clientID string, address string) error { userID, client := s.t.getIMAPClient(clientID) - return client.Login(address, s.t.getUserBridgePass(userID)) + return client.Login(address, s.t.getUserByID(userID).getBridgePass()) } func (s *scenario) imapClientCannotAuthenticate(clientID string) error { userID, client := s.t.getIMAPClient(clientID) - if err := client.Login(s.t.getUserAddrs(userID)[0], s.t.getUserBridgePass(userID)); err == nil { + if err := client.Login(s.t.getUserByID(userID).getEmails()[0], s.t.getUserByID(userID).getBridgePass()); err == nil { return fmt.Errorf("expected error, got nil") } @@ -98,7 +98,7 @@ func (s *scenario) imapClientCannotAuthenticate(clientID string) error { func (s *scenario) imapClientCannotAuthenticateWithAddress(clientID, address string) error { userID, client := s.t.getIMAPClient(clientID) - if err := client.Login(address, s.t.getUserBridgePass(userID)); err == nil { + if err := client.Login(address, s.t.getUserByID(userID).getBridgePass()); err == nil { return fmt.Errorf("expected error, got nil") } @@ -108,7 +108,7 @@ func (s *scenario) imapClientCannotAuthenticateWithAddress(clientID, address str func (s *scenario) imapClientCannotAuthenticateWithIncorrectUsername(clientID string) error { userID, client := s.t.getIMAPClient(clientID) - if err := client.Login(s.t.getUserAddrs(userID)[0]+"bad", s.t.getUserBridgePass(userID)); err == nil { + if err := client.Login(s.t.getUserByID(userID).getEmails()[0]+"bad", s.t.getUserByID(userID).getBridgePass()); err == nil { return fmt.Errorf("expected error, got nil") } @@ -118,7 +118,7 @@ func (s *scenario) imapClientCannotAuthenticateWithIncorrectUsername(clientID st func (s *scenario) imapClientCannotAuthenticateWithIncorrectPassword(clientID string) error { userID, client := s.t.getIMAPClient(clientID) - if err := client.Login(s.t.getUserAddrs(userID)[0], s.t.getUserBridgePass(userID)+"bad"); err == nil { + if err := client.Login(s.t.getUserByID(userID).getEmails()[0], s.t.getUserByID(userID).getBridgePass()+"bad"); err == nil { return fmt.Errorf("expected error, got nil") } diff --git a/tests/smtp_test.go b/tests/smtp_test.go index 26c1fe29..430b83af 100644 --- a/tests/smtp_test.go +++ b/tests/smtp_test.go @@ -27,25 +27,25 @@ import ( ) func (s *scenario) userConnectsSMTPClient(username, clientID string) error { - return s.t.newSMTPClient(s.t.getUserID(username), clientID) + return s.t.newSMTPClient(s.t.getUserByName(username).getUserID(), clientID) } func (s *scenario) userConnectsSMTPClientOnPort(username, clientID string, port int) error { - return s.t.newSMTPClientOnPort(s.t.getUserID(username), clientID, port) + return s.t.newSMTPClientOnPort(s.t.getUserByName(username).getUserID(), clientID, port) } func (s *scenario) userConnectsAndAuthenticatesSMTPClient(username, clientID string) error { - return s.userConnectsAndAuthenticatesSMTPClientWithAddress(username, clientID, s.t.getUserAddrs(s.t.getUserID(username))[0]) + return s.userConnectsAndAuthenticatesSMTPClientWithAddress(username, clientID, s.t.getUserByName(username).getEmails()[0]) } func (s *scenario) userConnectsAndAuthenticatesSMTPClientWithAddress(username, clientID, address string) error { - if err := s.t.newSMTPClient(s.t.getUserID(username), clientID); err != nil { + if err := s.t.newSMTPClient(s.t.getUserByName(username).getUserID(), clientID); err != nil { return err } userID, client := s.t.getSMTPClient(clientID) - s.t.pushError(client.Auth(smtp.PlainAuth("", address, s.t.getUserBridgePass(userID), constants.Host))) + s.t.pushError(client.Auth(smtp.PlainAuth("", address, s.t.getUserByID(userID).getBridgePass(), constants.Host))) return nil } @@ -53,7 +53,7 @@ func (s *scenario) userConnectsAndAuthenticatesSMTPClientWithAddress(username, c func (s *scenario) smtpClientCanAuthenticate(clientID string) error { userID, client := s.t.getSMTPClient(clientID) - if err := client.Auth(smtp.PlainAuth("", s.t.getUserAddrs(userID)[0], s.t.getUserBridgePass(userID), constants.Host)); err != nil { + if err := client.Auth(smtp.PlainAuth("", s.t.getUserByID(userID).getEmails()[0], s.t.getUserByID(userID).getBridgePass(), constants.Host)); err != nil { return fmt.Errorf("expected no error, got %v", err) } @@ -63,7 +63,7 @@ func (s *scenario) smtpClientCanAuthenticate(clientID string) error { func (s *scenario) smtpClientCannotAuthenticate(clientID string) error { userID, client := s.t.getSMTPClient(clientID) - if err := client.Auth(smtp.PlainAuth("", s.t.getUserAddrs(userID)[0], s.t.getUserBridgePass(userID), constants.Host)); err == nil { + if err := client.Auth(smtp.PlainAuth("", s.t.getUserByID(userID).getEmails()[0], s.t.getUserByID(userID).getBridgePass(), constants.Host)); err == nil { return fmt.Errorf("expected error, got nil") } @@ -73,7 +73,7 @@ func (s *scenario) smtpClientCannotAuthenticate(clientID string) error { func (s *scenario) smtpClientCannotAuthenticateWithIncorrectUsername(clientID string) error { userID, client := s.t.getSMTPClient(clientID) - if err := client.Auth(smtp.PlainAuth("", s.t.getUserAddrs(userID)[0]+"bad", s.t.getUserBridgePass(userID), constants.Host)); err == nil { + if err := client.Auth(smtp.PlainAuth("", s.t.getUserByID(userID).getEmails()[0]+"bad", s.t.getUserByID(userID).getBridgePass(), constants.Host)); err == nil { return fmt.Errorf("expected error, got nil") } @@ -83,7 +83,7 @@ func (s *scenario) smtpClientCannotAuthenticateWithIncorrectUsername(clientID st func (s *scenario) smtpClientCannotAuthenticateWithIncorrectPassword(clientID string) error { userID, client := s.t.getSMTPClient(clientID) - if err := client.Auth(smtp.PlainAuth("", s.t.getUserAddrs(userID)[0], s.t.getUserBridgePass(userID)+"bad", constants.Host)); err == nil { + if err := client.Auth(smtp.PlainAuth("", s.t.getUserByID(userID).getEmails()[0], s.t.getUserByID(userID).getBridgePass()+"bad", constants.Host)); err == nil { return fmt.Errorf("expected error, got nil") } diff --git a/tests/types_test.go b/tests/types_test.go index dfb8eb7d..c59b13dc 100644 --- a/tests/types_test.go +++ b/tests/types_test.go @@ -21,6 +21,7 @@ import ( "bytes" "fmt" "io" + "os" "reflect" "strconv" "strings" @@ -49,6 +50,9 @@ type Message struct { Unread bool `bdd:"unread"` Deleted bool `bdd:"deleted"` + + InReplyTo string `bdd:"in-reply-to"` + References string `bdd:"references"` } func (msg Message) Build() []byte { @@ -74,6 +78,14 @@ func (msg Message) Build() []byte { b = append(b, "Subject: "+msg.Subject+"\r\n"...) } + if msg.InReplyTo != "" { + b = append(b, "In-Reply-To: "+msg.InReplyTo+"\r\n"...) + } + + if msg.References != "" { + b = append(b, "References: "+msg.References+"\r\n"...) + } + if msg.Date != "" { date, err := time.Parse(time.RFC822, msg.Date) if err != nil { @@ -125,6 +137,9 @@ func newMessageFromIMAP(msg *imap.Message) Message { Unread: !slices.Contains(msg.Flags, imap.SeenFlag), Deleted: slices.Contains(msg.Flags, imap.DeletedFlag), Date: msg.Envelope.Date.Format(time.RFC822Z), + InReplyTo: msg.Envelope.InReplyTo, + // Go-imap only supports in-reply-to so we have to mimic other client by using it as references. + References: msg.Envelope.InReplyTo, } if len(msg.Envelope.From) > 0 { @@ -195,7 +210,13 @@ func matchMailboxes(have, want []Mailbox) error { func eventually(condition func() error) error { ch := make(chan error, 1) - timer := time.NewTimer(30 * time.Second) + var timerDuration = 30 * time.Second + // Extend to 5min for live API. + if hostURL := os.Getenv("FEATURE_TEST_HOST_URL"); hostURL != "" { + timerDuration = 600 * time.Second + } + + timer := time.NewTimer(timerDuration) defer timer.Stop() ticker := time.NewTicker(100 * time.Millisecond) diff --git a/tests/user_test.go b/tests/user_test.go index 044d52f4..3be34f5f 100644 --- a/tests/user_test.go +++ b/tests/user_test.go @@ -32,7 +32,6 @@ import ( "github.com/bradenaw/juniper/xslices" "github.com/cucumber/godog" "github.com/google/uuid" - "golang.org/x/exp/slices" ) func (s *scenario) thereExistsAnAccountWithUsernameAndPassword(username, password string) error { @@ -52,7 +51,7 @@ func (s *scenario) theAccountHasAdditionalDisabledAddress(username, address stri } func (s *scenario) theAccountHasAdditionalAddressWithoutKeys(username, address string) error { - userID := s.t.getUserID(username) + userID := s.t.getUserByName(username).getUserID() // Decrypt the user's encrypted ID for use with quark. userDecID, err := s.t.runQuarkCmd(context.Background(), "encryption:id", "--decrypt", userID) @@ -65,7 +64,8 @@ func (s *scenario) theAccountHasAdditionalAddressWithoutKeys(username, address s context.Background(), "user:create:address", string(userDecID), - s.t.getUserPass(userID), + s.t.getUserByID(userID).getUserPass(), + address, ); err != nil { return err @@ -78,15 +78,15 @@ func (s *scenario) theAccountHasAdditionalAddressWithoutKeys(username, address s } // Set the new address of the user. - s.t.setUserAddr(userID, addr[len(addr)-1].ID, address) + s.t.getUserByID(userID).addAddress(addr[len(addr)-1].ID, address) return nil }) } func (s *scenario) theAccountNoLongerHasAdditionalAddress(username, address string) error { - userID := s.t.getUserID(username) - addrID := s.t.getUserAddrID(userID, address) + userID := s.t.getUserByName(username).getUserID() + addrID := s.t.getUserByName(username).getAddrID(address) if err := s.t.withClient(context.Background(), username, func(ctx context.Context, c *proton.Client) error { if err := c.DisableAddress(ctx, addrID); err != nil { @@ -98,7 +98,7 @@ func (s *scenario) theAccountNoLongerHasAdditionalAddress(username, address stri return err } - s.t.unsetUserAddr(userID, addrID) + s.t.getUserByID(userID).remAddress(addrID) return nil } @@ -184,8 +184,8 @@ func (s *scenario) theAddressOfAccountHasTheFollowingMessagesInMailbox(address, ctx, cancel := context.WithCancel(context.Background()) defer cancel() - userID := s.t.getUserID(username) - addrID := s.t.getUserAddrID(userID, address) + userID := s.t.getUserByName(username).getUserID() + addrID := s.t.getUserByName(username).getAddrID(address) mboxID := s.t.getMBoxID(userID, mailbox) wantMessages, err := unmarshalTable[Message](table) @@ -193,14 +193,6 @@ func (s *scenario) theAddressOfAccountHasTheFollowingMessagesInMailbox(address, return err } - var messageFlags proton.MessageFlag - - if !strings.EqualFold(mailbox, "Sent") { - messageFlags = proton.MessageFlagReceived - } else { - messageFlags = proton.MessageFlagSent - } - return s.t.createMessages(ctx, username, addrID, xslices.Map(wantMessages, func(message Message) proton.ImportReq { return proton.ImportReq{ @@ -208,7 +200,7 @@ func (s *scenario) theAddressOfAccountHasTheFollowingMessagesInMailbox(address, AddressID: addrID, LabelIDs: []string{mboxID}, Unread: proton.Bool(message.Unread), - Flags: messageFlags, + Flags: flagsForMailbox(mailbox), }, Message: message.Build(), } @@ -219,8 +211,8 @@ func (s *scenario) theAddressOfAccountHasMessagesInMailbox(address, username str ctx, cancel := context.WithCancel(context.Background()) defer cancel() - userID := s.t.getUserID(username) - addrID := s.t.getUserAddrID(userID, address) + userID := s.t.getUserByName(username).getUserID() + addrID := s.t.getUserByName(username).getAddrID(address) mboxID := s.t.getMBoxID(userID, mailbox) return s.t.createMessages(ctx, username, addrID, iterator.Collect(iterator.Map(iterator.Counter(count), func(idx int) proton.ImportReq { @@ -228,7 +220,7 @@ func (s *scenario) theAddressOfAccountHasMessagesInMailbox(address, username str Metadata: proton.ImportMetadata{ AddressID: addrID, LabelIDs: []string{mboxID}, - Flags: proton.MessageFlagReceived, + Flags: flagsForMailbox(mailbox), }, Message: Message{ Subject: fmt.Sprintf("%d", idx), @@ -240,6 +232,18 @@ func (s *scenario) theAddressOfAccountHasMessagesInMailbox(address, username str }))) } +func flagsForMailbox(mailboxName string) proton.MessageFlag { + if strings.EqualFold(mailboxName, "Sent") { + return proton.MessageFlagSent + } + + if strings.EqualFold(mailboxName, "Scheduled") { + return proton.MessageFlagScheduledSend + } + + return proton.MessageFlagReceived +} + // accountDraftChanged changes the draft attributes, where draftIndex is // similar to sequential ID i.e. 1 represents the first message of draft folder // sorted by API creation time. @@ -262,7 +266,7 @@ func (s *scenario) theFollowingFieldsWereChangedInDraftForAddressOfAccount(draft defer cancel() return s.t.withClient(ctx, username, func(ctx context.Context, c *proton.Client) error { - return s.t.withAddrKR(ctx, c, username, s.t.getUserAddrID(s.t.getUserID(username), address), func(_ context.Context, addrKR *crypto.KeyRing) error { + return s.t.withAddrKR(ctx, c, username, s.t.getUserByName(username).getAddrID(address), func(_ context.Context, addrKR *crypto.KeyRing) error { var changes proton.DraftTemplate if wantMessages[0].From != "" { @@ -311,7 +315,7 @@ func (s *scenario) drafAtIndexWasMovedToTrashForAddressOfAccount(draftIndex int, defer cancel() return s.t.withClient(ctx, username, func(ctx context.Context, c *proton.Client) error { - return s.t.withAddrKR(ctx, c, username, s.t.getUserAddrID(s.t.getUserID(username), address), func(_ context.Context, addrKR *crypto.KeyRing) error { + return s.t.withAddrKR(ctx, c, username, s.t.getUserByName(username).getAddrID(address), func(_ context.Context, addrKR *crypto.KeyRing) error { if err := c.UnlabelMessages(ctx, []string{draftID}, proton.DraftsLabel); err != nil { return fmt.Errorf("failed to unlabel draft") } @@ -329,7 +333,7 @@ func (s *scenario) userLogsInWithUsernameAndPassword(username, password string) if err != nil { s.t.pushError(err) } else { - if userID != s.t.getUserID(username) { + if userID != s.t.getUserByName(username).getUserID() { return errors.New("user ID mismatch") } @@ -338,18 +342,18 @@ func (s *scenario) userLogsInWithUsernameAndPassword(username, password string) return err } - s.t.setUserBridgePass(userID, info.BridgePass) + s.t.getUserByID(userID).setBridgePass(string(info.BridgePass)) } return nil } func (s *scenario) userLogsOut(username string) error { - return s.t.bridge.LogoutUser(context.Background(), s.t.getUserID(username)) + return s.t.bridge.LogoutUser(context.Background(), s.t.getUserByName(username).getUserID()) } func (s *scenario) userIsDeleted(username string) error { - return s.t.bridge.DeleteUser(context.Background(), s.t.getUserID(username)) + return s.t.bridge.DeleteUser(context.Background(), s.t.getUserByName(username).getUserID()) } func (s *scenario) theAuthOfUserIsRevoked(username string) error { @@ -359,7 +363,7 @@ func (s *scenario) theAuthOfUserIsRevoked(username string) error { } func (s *scenario) userIsListedAndConnected(username string) error { - user, err := s.t.bridge.GetUserInfo(s.t.getUserID(username)) + user, err := s.t.bridge.GetUserInfo(s.t.getUserByName(username).getUserID()) if err != nil { return err } @@ -382,7 +386,7 @@ func (s *scenario) userIsEventuallyListedAndConnected(username string) error { } func (s *scenario) userIsListedButNotConnected(username string) error { - user, err := s.t.bridge.GetUserInfo(s.t.getUserID(username)) + user, err := s.t.bridge.GetUserInfo(s.t.getUserByName(username).getUserID()) if err != nil { return err } @@ -399,7 +403,7 @@ func (s *scenario) userIsListedButNotConnected(username string) error { } func (s *scenario) userIsNotListed(username string) error { - if slices.Contains(s.t.bridge.GetUserIDs(), s.t.getUserID(username)) { + if _, err := s.t.bridge.QueryUserInfo(username); !errors.Is(err, bridge.ErrNoSuchUser) { return errors.New("user listed") } @@ -411,7 +415,7 @@ func (s *scenario) userFinishesSyncing(username string) error { } func (s *scenario) addAdditionalAddressToAccount(username, address string, disabled bool) error { - userID := s.t.getUserID(username) + userID := s.t.getUserByName(username).getUserID() // Decrypt the user's encrypted ID for use with quark. userDecID, err := s.t.runQuarkCmd(context.Background(), "encryption:id", "--decrypt", userID) @@ -429,7 +433,7 @@ func (s *scenario) addAdditionalAddressToAccount(username, address string, disab args = append(args, string(userDecID), - s.t.getUserPass(userID), + s.t.getUserByID(userID).getUserPass(), address, ) @@ -449,7 +453,7 @@ func (s *scenario) addAdditionalAddressToAccount(username, address string, disab } // Set the new address of the user. - s.t.setUserAddr(userID, addr[len(addr)-1].ID, address) + s.t.getUserByID(userID).addAddress(addr[len(addr)-1].ID, address) return nil }) @@ -503,14 +507,11 @@ func (s *scenario) createUserAccount(username, password string, disabled bool) e return err } - // Set the ID of the user. - s.t.setUserID(username, user.ID) - - // Set the password of the user. - s.t.setUserPass(user.ID, password) + // Add the test user. + s.t.addUser(user.ID, username, password) // Set the address of the user. - s.t.setUserAddr(user.ID, addr[0].ID, addr[0].Email) + s.t.getUserByID(user.ID).addAddress(addr[0].ID, addr[0].Email) return nil }) diff --git a/thing b/thing new file mode 100644 index 00000000..8713bc57 --- /dev/null +++ b/thing @@ -0,0 +1 @@ +{"Code":1001,"Responses":[{"LinkID":"hz-Ozxfk3KMyrISzrtKrZg-Vj7Q2iJcXqyONMryqaVyJZcg90RfEeV-1jFKFIXEmPniBsSAPgHoJQfLyTgpc0A==","Response":{"Code":1000}},{"LinkID":"T8mgwjkPPWbpmlkVJdHJIIAcgb3YR8ZW-OnAi8kWaKh6wubRnC8-S5zBxIktELywVxECmssJ9E-wPRj06zPWmg==","Response":{"Code":1000}}]} diff --git a/utils/hasher/main.go b/utils/hasher/main.go index 6bc924f4..1b56df6e 100644 --- a/utils/hasher/main.go +++ b/utils/hasher/main.go @@ -32,7 +32,7 @@ func main() { } } -func createApp() *cli.App { //nolint:funlen +func createApp() *cli.App { app := cli.NewApp() app.Name = "hasher" diff --git a/utils/versioner/main.go b/utils/versioner/main.go index b847b049..0ff57d62 100644 --- a/utils/versioner/main.go +++ b/utils/versioner/main.go @@ -42,7 +42,7 @@ func main() { } } -func createApp() *cli.App { //nolint:funlen +func createApp() *cli.App { app := cli.NewApp() app.Name = "versioner"