mirror of
https://github.com/ProtonMail/proton-bridge.git
synced 2025-12-10 12:46:46 +00:00
Compare commits
192 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 61e4ca5814 | |||
| 8e0693ab03 | |||
| a3d2df9d38 | |||
| f9f4ce996d | |||
| fc69b9aabb | |||
| f7ed3abcfe | |||
| c2ffae3799 | |||
| 437b7a4cfe | |||
| df601ecbbd | |||
| e9c05c5a6b | |||
| 22f427d522 | |||
| d356f306d9 | |||
| e717b69139 | |||
| 1dbd37d05b | |||
| 7dad6cc9a4 | |||
| 0332a3f873 | |||
| 20a0404efb | |||
| 29b7530ddf | |||
| 27e7d7967d | |||
| d40fbda2ab | |||
| 3bb9146d9f | |||
| f0d05aeb79 | |||
| ad6b84d4e0 | |||
| 38031b2fb9 | |||
| b74dba884f | |||
| 7276c23b2b | |||
| f0b1ab55a2 | |||
| f851f4d5c2 | |||
| f2d568d92f | |||
| a0dc764bb9 | |||
| 55beb9227f | |||
| 6ed97a0347 | |||
| 7ce3529f5d | |||
| ed9edb3620 | |||
| f30269865d | |||
| d7c5ace8e4 | |||
| b82e2ca176 | |||
| 5af3e930ec | |||
| 72cd641c72 | |||
| 4a2ac813d3 | |||
| 961742fa53 | |||
| 9984165798 | |||
| 551f5c3c18 | |||
| 41f2ffa4ec | |||
| 778b17c44e | |||
| 5d51cc1739 | |||
| a7270102af | |||
| ca5c45b1c5 | |||
| 31920a4468 | |||
| 2e52b8db87 | |||
| 2899e7bb15 | |||
| 41e15db442 | |||
| b5b477a3ce | |||
| 07b7fa7364 | |||
| 5637ca2529 | |||
| a93a8e7be9 | |||
| db7ead3901 | |||
| 42ced6694e | |||
| af0c5e6bae | |||
| cf6ed81a00 | |||
| 8c9d5c54fc | |||
| 36ec9b07e0 | |||
| d9847ddd6a | |||
| e1747357bc | |||
| 77e352a101 | |||
| b41c4d2fa6 | |||
| b6ad1fe490 | |||
| bc21bb1d8d | |||
| 8ea610c625 | |||
| 10da4f284c | |||
| e49d2e1be7 | |||
| b259de238e | |||
| ea821b1bd8 | |||
| 94347d95df | |||
| ef051d5ed6 | |||
| e40e8e3e75 | |||
| 9d0368de97 | |||
| c5699700b3 | |||
| 1141ea27e2 | |||
| ecc1c34b16 | |||
| 3601adcae6 | |||
| d11cf57879 | |||
| 2c8feff97a | |||
| b7adccf651 | |||
| 726c8918ab | |||
| 29af8e7178 | |||
| 18257f0302 | |||
| aeceb7d593 | |||
| 85c06809d2 | |||
| f65e050588 | |||
| e0d07d67a0 | |||
| 0a9748a15d | |||
| 6bd0739013 | |||
| 5cb893fc1b | |||
| f5624c9932 | |||
| 2b1daa60bb | |||
| ffb18adfd0 | |||
| 649195cc2b | |||
| b0ce46ca8a | |||
| 6435f7b09a | |||
| 59075f2e26 | |||
| 1d9855a190 | |||
| cea33bebe2 | |||
| 9d405a1549 | |||
| 47f468e4b7 | |||
| b9c6c00709 | |||
| 5ce9cb8eec | |||
| bc7133e401 | |||
| a219ecf3cb | |||
| 8061b1e6fa | |||
| 6509df523f | |||
| 0d1abaec0d | |||
| 4d1ace5de7 | |||
| 1250621a4d | |||
| 8d6e55ba54 | |||
| a4a29cbf82 | |||
| 39bccc2747 | |||
| 0cf1b38c2b | |||
| 6b7e706100 | |||
| bb90ed5446 | |||
| 107843d58f | |||
| 63f089540e | |||
| c35ff4f913 | |||
| cd6b5cdcc3 | |||
| 8f1ca00c9d | |||
| f21f583d05 | |||
| e940d9f6fe | |||
| 1ed8e939b8 | |||
| b5d63783f7 | |||
| e0113ec267 | |||
| 22d2bcc21d | |||
| 91dcb2f773 | |||
| c676c732ab | |||
| 444f2d8a12 | |||
| f10da3c7f0 | |||
| b8dd9f82bd | |||
| 1157e60972 | |||
| e9e4d8c725 | |||
| 186fa24106 | |||
| 63780b7b8d | |||
| e3e4769d78 | |||
| b2e9c4e4e9 | |||
| db4cb36538 | |||
| 984864553e | |||
| 2707a5627c | |||
| 8e0d6d41a6 | |||
| 2b76a45e17 | |||
| 7ab54da8c4 | |||
| 2cce29e858 | |||
| ef1223391b | |||
| fb98a797ba | |||
| 6be31bdeb2 | |||
| fce5990d19 | |||
| 1d4ee0c33e | |||
| a3e102e456 | |||
| 21dcac9fac | |||
| f0ee82fdd2 | |||
| 0c6a098af9 | |||
| 5bf359d34f | |||
| 6d784f2444 | |||
| 835bf1e77f | |||
| df5fbda72f | |||
| c482f768d9 | |||
| 21cf7459c9 | |||
| cf1ba6588a | |||
| 858f2c7f29 | |||
| f63238faed | |||
| f6ff85f69d | |||
| ec5b5939b9 | |||
| dec00ff9cc | |||
| 9fddd77f0d | |||
| aae65c9d38 | |||
| f3b197fa56 | |||
| 0a9ce5f526 | |||
| a2029002c4 | |||
| 7c41c8e23a | |||
| 36fdb88d96 | |||
| c69239ca16 | |||
| e10aa89313 | |||
| d0a97a3f4a | |||
| e01dc77a61 | |||
| 509ba52ba2 | |||
| c37a0338c5 | |||
| 9f23d5a6f4 | |||
| 3f50bf66f4 | |||
| 233c55ab19 | |||
| cb30dd91e3 | |||
| 41d82e10f9 | |||
| 8496c9e181 | |||
| 3dadad5131 | |||
| 00146e7474 | |||
| 12ac47e949 |
33
.gitignore
vendored
33
.gitignore
vendored
@ -6,9 +6,6 @@
|
||||
.*.sw?
|
||||
*~
|
||||
|
||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
vendor
|
||||
|
||||
# Test files
|
||||
godog.test
|
||||
debug.test
|
||||
@ -17,17 +14,12 @@ coverage.html
|
||||
# Run files
|
||||
mem.pprof
|
||||
|
||||
# Auto generated frontend
|
||||
internal/frontend/qml/BridgeUI/*.qmlc
|
||||
internal/frontend/qml/ImportExportUI/*.qmlc
|
||||
internal/frontend/qml/ProtonUI/*.qmlc
|
||||
internal/frontend/qml/ProtonUI/fontawesome.ttf
|
||||
internal/frontend/qml/ProtonUI/images
|
||||
internal/frontend/qml/ImportExportUI/images
|
||||
frontend/qml/*.qmlc
|
||||
|
||||
# Credits files (generated).
|
||||
# Auto generated
|
||||
internal/**/credits.go
|
||||
vendor
|
||||
vendor-cache
|
||||
/main.go
|
||||
|
||||
|
||||
# Build files
|
||||
/launcher-*
|
||||
@ -37,18 +29,3 @@ internal/**/credits.go
|
||||
/hasher
|
||||
cmd/Desktop-Bridge/deploy
|
||||
cmd/Import-Export/deploy
|
||||
internal/frontend/qt*/moc.cpp
|
||||
internal/frontend/qt*/moc.go
|
||||
internal/frontend/qt*/moc.h
|
||||
internal/frontend/qt*/moc_cgo_*.go
|
||||
internal/frontend/qt*/moc_moc.h
|
||||
internal/frontend/qt*/rcc.cpp
|
||||
internal/frontend/qt*/rcc.qrc
|
||||
internal/frontend/qt*/rcc_cgo_*.go
|
||||
|
||||
internal/frontend/rcc.cpp
|
||||
internal/frontend/rcc.qrc
|
||||
internal/frontend/rcc_cgo_*.go
|
||||
vendor-cache/
|
||||
|
||||
/main.go
|
||||
|
||||
142
.gitlab-ci.yml
142
.gitlab-ci.yml
@ -48,7 +48,7 @@ lint:
|
||||
tags:
|
||||
- medium
|
||||
|
||||
test:
|
||||
test-linux:
|
||||
stage: test
|
||||
only:
|
||||
- branches
|
||||
@ -65,6 +65,14 @@ test:
|
||||
tags:
|
||||
- medium
|
||||
|
||||
test-windows:
|
||||
extends: .build-windows-base
|
||||
stage: test
|
||||
only:
|
||||
- branches
|
||||
script:
|
||||
- make test
|
||||
|
||||
test-integration:
|
||||
stage: test
|
||||
only:
|
||||
@ -81,15 +89,37 @@ dependency-updates:
|
||||
|
||||
# Stage: BUILD
|
||||
|
||||
build-qml:
|
||||
tags:
|
||||
- small
|
||||
only:
|
||||
- branches
|
||||
stage: build
|
||||
artifacts:
|
||||
name: "bridge-qml-$CI_COMMIT_SHORT_SHA"
|
||||
expire_in: 1 day
|
||||
paths:
|
||||
- bridge_qml.tgz
|
||||
script:
|
||||
- cd internal/frontend/qml
|
||||
- tar -cvzf ../../../bridge_qml.tgz ./*
|
||||
|
||||
|
||||
.build-base:
|
||||
stage: build
|
||||
only:
|
||||
- branches
|
||||
- manual
|
||||
before_script:
|
||||
- mkdir -p .cache/bin
|
||||
- export PATH=$(pwd)/.cache/bin:$PATH
|
||||
- export GOPATH="$CI_PROJECT_DIR/.cache"
|
||||
script:
|
||||
- make build
|
||||
- git diff && git diff-index --quiet HEAD
|
||||
artifacts:
|
||||
# Note: The latest artifacts for refs are locked against deletion, and kept regardless of the expiry time.
|
||||
# Introduced in GitLab 13.0 behind a disabled feature flag, and made the default behavior in GitLab 13.4.
|
||||
# Note: The latest artifacts for refs are locked against deletion, and kept
|
||||
# regardless of the expiry time. Introduced in GitLab 13.0 behind a
|
||||
# disabled feature flag, and made the default behavior in GitLab 13.4.
|
||||
expire_in: 1 day
|
||||
tags:
|
||||
- large
|
||||
@ -105,6 +135,7 @@ build-linux-qa:
|
||||
extends: .build-base
|
||||
only:
|
||||
- web
|
||||
- branches
|
||||
script:
|
||||
- BUILD_TAGS="build_qa" make build
|
||||
artifacts:
|
||||
@ -112,25 +143,6 @@ build-linux-qa:
|
||||
paths:
|
||||
- bridge_*.tgz
|
||||
|
||||
build-ie-linux:
|
||||
extends: .build-base
|
||||
script:
|
||||
- make build-ie
|
||||
artifacts:
|
||||
name: "ie-linux-$CI_COMMIT_SHORT_SHA"
|
||||
paths:
|
||||
- ie_*.tgz
|
||||
|
||||
build-ie-linux-qa:
|
||||
extends: .build-base
|
||||
only:
|
||||
- web
|
||||
script:
|
||||
- BUILD_TAGS="build_qa" make build-ie
|
||||
artifacts:
|
||||
name: "ie-linux-qa-$CI_COMMIT_SHORT_SHA"
|
||||
paths:
|
||||
- ie_*.tgz
|
||||
|
||||
.build-darwin-base:
|
||||
extends: .build-base
|
||||
@ -160,6 +172,7 @@ build-darwin-qa:
|
||||
extends: .build-darwin-base
|
||||
only:
|
||||
- web
|
||||
- branches
|
||||
script:
|
||||
- BUILD_TAGS="build_qa" make build
|
||||
artifacts:
|
||||
@ -167,43 +180,23 @@ build-darwin-qa:
|
||||
paths:
|
||||
- bridge_*.tgz
|
||||
|
||||
build-ie-darwin:
|
||||
extends: .build-darwin-base
|
||||
script:
|
||||
- make build-ie
|
||||
artifacts:
|
||||
name: "ie-darwin-$CI_COMMIT_SHORT_SHA"
|
||||
paths:
|
||||
- ie_*.tgz
|
||||
|
||||
build-ie-darwin-qa:
|
||||
extends: .build-darwin-base
|
||||
only:
|
||||
- web
|
||||
script:
|
||||
- BUILD_TAGS="build_qa" make build-ie
|
||||
artifacts:
|
||||
name: "ie-darwin-qa-$CI_COMMIT_SHORT_SHA"
|
||||
paths:
|
||||
- ie_*.tgz
|
||||
|
||||
.build-windows-base:
|
||||
extends: .build-base
|
||||
services:
|
||||
- docker:dind
|
||||
variables:
|
||||
DOCKER_HOST: tcp://docker:2375
|
||||
before_script:
|
||||
- export GOROOT=/c/Go
|
||||
- export PATH=$GOROOT/bin:$PATH
|
||||
- export GOARCH=amd64
|
||||
- export GOPATH=~/go
|
||||
- export GO111MODULE=on
|
||||
- export PATH=$GOPATH/bin:$PATH
|
||||
- export MSYSTEM=
|
||||
- export PATH=$PATH:/c/grrrQt/5.13.2/mingw73_64/bin
|
||||
tags:
|
||||
- windows-bridge
|
||||
|
||||
build-windows:
|
||||
extends: .build-windows-base
|
||||
script:
|
||||
# We need to install docker because qtdeploy builds for windows inside a docker container.
|
||||
# Docker will connect to the dockerd daemon provided by the runner service docker:dind at tcp://docker:2375.
|
||||
- curl -fsSL https://get.docker.com -o get-docker.sh && sh get-docker.sh
|
||||
- apt-get update && apt-get -y install binutils-mingw-w64 tar gzip
|
||||
- ln -s /usr/bin/x86_64-w64-mingw32-windres /usr/bin/windres
|
||||
- go mod download
|
||||
- TARGET_OS=windows make build
|
||||
artifacts:
|
||||
name: "bridge-windows-$CI_COMMIT_SHORT_SHA"
|
||||
paths:
|
||||
@ -213,51 +206,14 @@ build-windows-qa:
|
||||
extends: .build-windows-base
|
||||
only:
|
||||
- web
|
||||
- branches
|
||||
script:
|
||||
# We need to install docker because qtdeploy builds for windows inside a docker container.
|
||||
# Docker will connect to the dockerd daemon provided by the runner service docker:dind at tcp://docker:2375.
|
||||
- curl -fsSL https://get.docker.com -o get-docker.sh && sh get-docker.sh
|
||||
- apt-get update && apt-get -y install binutils-mingw-w64 tar gzip
|
||||
- ln -s /usr/bin/x86_64-w64-mingw32-windres /usr/bin/windres
|
||||
- go mod download
|
||||
- TARGET_OS=windows BUILD_TAGS="build_qa" make build
|
||||
- BUILD_TAGS="build_qa" make build
|
||||
artifacts:
|
||||
name: "bridge-windows-qa-$CI_COMMIT_SHORT_SHA"
|
||||
paths:
|
||||
- bridge_*.tgz
|
||||
|
||||
build-ie-windows:
|
||||
extends: .build-windows-base
|
||||
script:
|
||||
# We need to install docker because qtdeploy builds for windows inside a docker container.
|
||||
# Docker will connect to the dockerd daemon provided by the runner service docker:dind at tcp://docker:2375.
|
||||
- curl -fsSL https://get.docker.com -o get-docker.sh && sh get-docker.sh
|
||||
- apt-get update && apt-get -y install binutils-mingw-w64 tar gzip
|
||||
- ln -s /usr/bin/x86_64-w64-mingw32-windres /usr/bin/windres
|
||||
- go mod download
|
||||
- TARGET_OS=windows make build-ie
|
||||
artifacts:
|
||||
name: "ie-windows-$CI_COMMIT_SHORT_SHA"
|
||||
paths:
|
||||
- ie_*.tgz
|
||||
|
||||
build-ie-windows-qa:
|
||||
extends: .build-windows-base
|
||||
only:
|
||||
- web
|
||||
script:
|
||||
# We need to install docker because qtdeploy builds for windows inside a docker container.
|
||||
# Docker will connect to the dockerd daemon provided by the runner service docker:dind at tcp://docker:2375.
|
||||
- curl -fsSL https://get.docker.com -o get-docker.sh && sh get-docker.sh
|
||||
- apt-get update && apt-get -y install binutils-mingw-w64 tar gzip
|
||||
- ln -s /usr/bin/x86_64-w64-mingw32-windres /usr/bin/windres
|
||||
- go mod download
|
||||
- TARGET_OS=windows BUILD_TAGS="build_qa" make build-ie
|
||||
artifacts:
|
||||
name: "ie-windows-qa-$CI_COMMIT_SHORT_SHA"
|
||||
paths:
|
||||
- ie_*.tgz
|
||||
|
||||
# Stage: MIRROR
|
||||
|
||||
mirror-repo:
|
||||
|
||||
@ -1,8 +1,6 @@
|
||||
---
|
||||
run:
|
||||
timeout: 10m
|
||||
build-tags:
|
||||
- nogui
|
||||
skip-dirs:
|
||||
- pkg/mime
|
||||
|
||||
|
||||
220
Changelog.md
220
Changelog.md
@ -2,6 +2,224 @@
|
||||
|
||||
Changelog [format](http://keepachangelog.com/en/1.0.0/)
|
||||
|
||||
## [Bridge 2.1.0] London
|
||||
|
||||
## Added
|
||||
* GODT-1376: Add first userID to sentry scope.
|
||||
* GODT-1375: Add host architecture to sentry reports.
|
||||
* GODT-1364: Add windows CI machine for tests, and build.
|
||||
|
||||
### Fixed
|
||||
* GODT-1499: Remove message from DB once it is not on server any more.
|
||||
|
||||
|
||||
## [Bridge 2.1.0] London
|
||||
|
||||
### Fixed
|
||||
* GODT-1482: Comment or mitigate panics, unlock cache when needed.
|
||||
* GODT-1481: Always turn off non-encrypted recipient report.
|
||||
|
||||
### Changed
|
||||
* GODT-1494: Update GopenPGP to 2.4.1.
|
||||
* GODT-1490: Update go sentry and openpgp.
|
||||
* GODT-1474: Optimising live integration tests.
|
||||
* GODT-1483: Correct scope in sentry report.
|
||||
* GODT-1477: Change CoD wording.
|
||||
|
||||
## Added
|
||||
* GODT-1478: Add GUI settings for keychain selection.
|
||||
* Other: Change copyright year.
|
||||
* GODT-1329: Dark mode, with autodetect.
|
||||
|
||||
|
||||
## [Bridge 2.0.1] Kwai
|
||||
|
||||
### Fixed
|
||||
* GODT-1468: Fix main windows status and add background context without retry.
|
||||
|
||||
|
||||
## [Bridge 2.0.0] Kwai
|
||||
|
||||
## Added
|
||||
* GODT-22: New GUI style and improved UX:
|
||||
* GODT-1168 GODT-1169 Qml artifacts for preview.
|
||||
* GODT-1177: Remove Import-Export from repo.
|
||||
* GODT-1167 GODT-1179 Make run-qml-preview.
|
||||
* GODT-1051: Add factory reset to bridge object.
|
||||
* GODT-1179 GODT-658: Components and login flows.
|
||||
* GODT-1051: Factory reset button.
|
||||
* GODT-1158: Adding cache on disk signals.
|
||||
* GODT-1298: Signal GUI is ready and rise window.
|
||||
* Other: Reactive show on startup.
|
||||
* GODT-1319: Set sourceSize everywhere for images.
|
||||
* GODT-1317 Use large png for systray and mark it as mask.
|
||||
* GODT-1346: GODT-1340 GODT-1315 QML changes.
|
||||
* GODT-1365: Create ComboBox component.
|
||||
* GODT-1338: GODT-1343 Help view buttons.
|
||||
* GODT-1340: Not crashing, user list updating in main thread.
|
||||
* GODT-1345: Adding panic handlers.
|
||||
* GODT-1271: Fix Status margings.
|
||||
* GODT-1320: Add loading property to each action within a notification.
|
||||
* GODT-1210: Add "free user" banner.
|
||||
* GODT-1314: Limit description field length within 150/800 bounds.
|
||||
* GODT-1250: Fix Port settings wording.
|
||||
* GODT-1369: Fix link render and wording in Help view.
|
||||
* GODT-1358: Fix wording.
|
||||
* GODT-1272: Fix status view layout.
|
||||
* GODT-1336: Fix showing window on startup.
|
||||
* GODT-175: Add option to attach logs for bug reports.
|
||||
* GODT-1272: Ultimate fix for MacOS transparency.
|
||||
* GODT-1384: Fix SettingsView scroll.
|
||||
* GODT-1385: Fix port setting.
|
||||
* GODT-1378: varia GUI fixes.
|
||||
* GODT-1390: Fix autostart toggle.
|
||||
* GODT-1251: Fix change SMTP settings.
|
||||
* GODT-1389: Fix buttons and banner layout.
|
||||
* GODT-1316: Set default TextArea and TextField behavior.
|
||||
* GODT-1244: Refactor switching stable-early and factory reset.
|
||||
* GODT-1351: Cache and update of space bytes in user object.
|
||||
* GODT-1351: Fix used size update from mail operations.
|
||||
* GODT-1411: refactor SettingView content to fill height.
|
||||
* GODT-1327: Reset cache path to default when disabling.
|
||||
* GODT-1412: Refactor paths and links.
|
||||
* GODT-1226: Fix status window position.
|
||||
* GODT-1366: Simple lookup of index and select current user.
|
||||
* GODT-1325: Add "already logged in" notification.
|
||||
* GODT-1391: Fix link colors across GUI.
|
||||
* GODT-1391: Fix color for avatar text.
|
||||
* GODT-1442: Fix "Sign In" button.
|
||||
* GODT-1428: Fix welcome illustration by using PNG.
|
||||
* GODT-1455 Adding links to setup guide.
|
||||
* GODT-1456: Make text selectable and clickable.
|
||||
* GODT-1459: Wording.
|
||||
* GODT-1460 GODT-1462: Adding delete account dialog and fixing status view brief and icon.
|
||||
* GODT-1458: Splash screen and wording.
|
||||
* GODT-1158: Caching encrypted full body messages on disk:
|
||||
* GODT-1433: Do not save message to cache if it's a draft.
|
||||
* GODT-1431 Do not cache message during new message event when CoD is off.
|
||||
* GODT-1381 Treat readonly folder as failure for cache on disk.
|
||||
* GODT-1431 Prevent watcher when not using disk on cache.
|
||||
* GODT-1381: Use in-memory cache in case local cache is unavailable.
|
||||
* GODT-1356 GODT-1302: Cache on disk concurency and API retries.
|
||||
* GODT-1332 Added tests for cache move functions.
|
||||
* GODT-1332: moved cache related functions to separate file.
|
||||
* GODT-1332 moving cache does not work on Windows.
|
||||
* GODT-1367: use waitgroup instead of channel in pool/pchan.
|
||||
* GODT-1367: Use sync.Once to only close pool jobs once.
|
||||
* GODT-1349: Change cache-related settings when enabling/disabling/moving cache.
|
||||
* GODT-1350: stop cacher/worker properly when logging out user.
|
||||
* GODT-1158: Store full messages bodies on disk.
|
||||
* GODT-1433 Adding first integration test for drafts.
|
||||
|
||||
## Changed
|
||||
* GODT-1438: Turn off SW OpenGL on windows and add debug info about graphic renderer.
|
||||
* GODT-1425: Factory reset enables launch on startup.
|
||||
* GODT-1433 Message.Type is deprecated, use Flags instead.
|
||||
* GODT-1388: Refactor Alternative routing.
|
||||
|
||||
|
||||
## [Bridge 1.8.12] James
|
||||
|
||||
### Fixed
|
||||
* GODT-1432: Check if keys are active before unlocking.
|
||||
|
||||
|
||||
## [Bridge 1.8.11] James
|
||||
|
||||
### Fixed
|
||||
* GODT-1415: Only messages which are in Spam should be moved to INBOX once they are marked as not-a-spam.
|
||||
* GODT-1405: Integration test fix: Prevent unilateral update in FETCH when copying message by append.
|
||||
* GODT-1392: Fix broken header fields for attachments.
|
||||
* GODT-1360: Fix live integration test.
|
||||
* GODT-968: Messages in All Mail should not be able to mark as deleted.
|
||||
* GODT-967: Append external message to All Mail should be APPEND to Archive instead.
|
||||
* GODT-966: Append internal message to AllMail should be no action.
|
||||
* GODT-965: MOVE command should end with error for All Mail.
|
||||
* GODT-963: STORE removing junk or adding nojunk should move message to inbox.
|
||||
|
||||
### Changed
|
||||
* GODT-1397: Update bbolt to v1.3.6.
|
||||
* GODT-1410: Remove event ID from sentry report description.
|
||||
* GODT-1395: CI should fail on go.sum changed.
|
||||
|
||||
|
||||
## [Bridge 1.8.10] James
|
||||
|
||||
### Fixed
|
||||
* GODT-1348: Max 100 conn per host.
|
||||
* GODT-1204: Handle importing too big messages.
|
||||
* GODT-1202: Do not update package if it's version older than launcher.
|
||||
* GODT-1318: Bump gopenpgp to v2.2.2, go-srp to v0.0.1, go-crypto to 52430bf6.
|
||||
* GODT-219: Update to godog v0.12.1.
|
||||
* GODT-1205: "RCPT TO" does not contain all addressed from "CC".
|
||||
* GODT-1103: Cleanup on windows when uninstalling Bridge.
|
||||
|
||||
|
||||
## [Bridge 1.8.9] James
|
||||
|
||||
### Fixed
|
||||
* GODT-1263: Fix crash on invalid or empty header.
|
||||
* GODT-1235: Fix 401 response error handling.
|
||||
* GODT-1261: Fix building messages with long key.
|
||||
* Other: use windows-compatible filename when dumping message in QA builds.
|
||||
|
||||
|
||||
## [Bridge 1.8.8] James
|
||||
|
||||
### Changed
|
||||
* GODT-1234 Set attachment name 'message.eml' for `message/rfc822` attachments.
|
||||
|
||||
|
||||
## [Bridge 1.8.7] James
|
||||
|
||||
### Changed
|
||||
* GODT-1201: Update gopenpgp to 2.1.10.
|
||||
|
||||
### Fixed
|
||||
* GODT-1193: Do not doubly encode parts.
|
||||
|
||||
|
||||
## [Bridge 1.8.6] James
|
||||
|
||||
### Removed
|
||||
* GODT-1187: Remove IMAP/SMTP blocking when no internet.
|
||||
|
||||
### Changed
|
||||
* GODT-1166: Reduce the number of auth for live test.
|
||||
|
||||
### Fixed
|
||||
* GODT-1193: Do not use message.Read permit non-UTF-8 charsets.
|
||||
|
||||
|
||||
## [Bridge 1.8.5] James
|
||||
|
||||
### Fixed
|
||||
* GODT-1189: Draft created on Outlook is synced on web.
|
||||
* GODT-1190: Fix some random crashes of Bridge on Windows.
|
||||
* GODT-1191: Fix data loss of some drafts messages when restarting outlook on Windows.
|
||||
|
||||
## [Bridge 1.8.4] James
|
||||
|
||||
### Added
|
||||
* GODT-1155: Update gopenpgp v2.1.9 and use go-srp.
|
||||
* GODT-1044: Lite parser for appended messages.
|
||||
* GODT-1183: Add test for getting contact emails by email.
|
||||
* GODT-1184: Preserve signatures in externally signed messages.
|
||||
|
||||
### Changed
|
||||
* GODT-949: Ignore some InvalidMediaParameter errors in lite parser.
|
||||
|
||||
### Fixed
|
||||
* GODT-1161: Guarantee order of responses when creating new message.
|
||||
* GODT-1162: Fix wrong section 1 error when email has no MIME parts.
|
||||
|
||||
|
||||
## [Bridge 1.8.3] James
|
||||
|
||||
### Fixed
|
||||
* GODT-1182: Use correct contact route.
|
||||
|
||||
|
||||
## [Bridge 1.8.2] James
|
||||
|
||||
### Fixed
|
||||
@ -20,11 +238,13 @@ Changelog [format](http://keepachangelog.com/en/1.0.0/)
|
||||
* GODT-1056 Check encrypted size of the message before upload.
|
||||
* GODT-1143 Turn off SMTP server while no connection.
|
||||
* GODT-1089 Explicitly open system preferences window on BigSur.
|
||||
* GODT-35: Connection manager with resty.
|
||||
|
||||
### Fixed
|
||||
* GODT-1159 SMTP server not restarting after restored internet.
|
||||
* GODT-1146 Refactor handling of fetching BODY[HEADER] (and similar) regarding trailing newline.
|
||||
* GODT-1152 Correctly resolve wildcard sequence/UID set.
|
||||
* GODT-876 Set default from if empty for importing draft.
|
||||
* Other: Avoid API jail.
|
||||
|
||||
|
||||
|
||||
79
Makefile
79
Makefile
@ -7,29 +7,16 @@ TARGET_CMD?=Desktop-Bridge
|
||||
TARGET_OS?=${GOOS}
|
||||
|
||||
## Build
|
||||
.PHONY: build build-ie build-nogui build-ie-nogui build-launcher build-launcher-ie versioner hasher
|
||||
.PHONY: build build-nogui build-launcher versioner hasher
|
||||
|
||||
# Keep version hardcoded so app build works also without Git repository.
|
||||
BRIDGE_APP_VERSION?=1.8.2+git
|
||||
IE_APP_VERSION?=1.3.3+git
|
||||
BRIDGE_APP_VERSION?=2.1.1+git
|
||||
APP_VERSION:=${BRIDGE_APP_VERSION}
|
||||
SRC_ICO:=logo.ico
|
||||
SRC_ICNS:=Bridge.icns
|
||||
SRC_SVG:=logo.svg
|
||||
TGT_ICNS:=Bridge.icns
|
||||
EXE_NAME:=proton-bridge
|
||||
CONFIGNAME:=bridge
|
||||
WINDRES_DEFINE:=BUILD_BRIDGE
|
||||
ifeq "${TARGET_CMD}" "Import-Export"
|
||||
APP_VERSION:=${IE_APP_VERSION}
|
||||
SRC_ICO:=ie.ico
|
||||
SRC_ICNS:=ie.icns
|
||||
SRC_SVG:=ie.svg
|
||||
TGT_ICNS:=ImportExport.icns
|
||||
EXE_NAME:=proton-ie
|
||||
CONFIGNAME:=importExport
|
||||
WINDRES_DEFINE:=BUILD_IE
|
||||
endif
|
||||
REVISION:=$(shell git rev-parse --short=10 HEAD)
|
||||
BUILD_TIME:=$(shell date +%FT%T%z)
|
||||
|
||||
@ -41,7 +28,6 @@ ifneq "${BUILD_LDFLAGS}" ""
|
||||
GO_LDFLAGS+=${BUILD_LDFLAGS}
|
||||
endif
|
||||
GO_LDFLAGS_LAUNCHER:=${GO_LDFLAGS}
|
||||
GO_LDFLAGS_LAUNCHER+=$(addprefix -X main.,ConfigName=${CONFIGNAME} ExeName=proton-${APP})
|
||||
ifeq "${TARGET_OS}" "windows"
|
||||
GO_LDFLAGS_LAUNCHER+=-H=windowsgui
|
||||
endif
|
||||
@ -70,9 +56,6 @@ EXE_TARGET:=${DEPLOY_DIR}/${TARGET_OS}/${EXE}
|
||||
EXE_QT_TARGET:=${DEPLOY_DIR}/${TARGET_OS}/${EXE_QT}
|
||||
|
||||
TGZ_TARGET:=bridge_${TARGET_OS}_${REVISION}.tgz
|
||||
ifeq "${TARGET_CMD}" "Import-Export"
|
||||
TGZ_TARGET:=ie_${TARGET_OS}_${REVISION}.tgz
|
||||
endif
|
||||
|
||||
ifdef QT_API
|
||||
VENDOR_TARGET:=prepare-vendor update-qt-docs
|
||||
@ -82,15 +65,9 @@ endif
|
||||
|
||||
build: ${TGZ_TARGET}
|
||||
|
||||
build-ie:
|
||||
TARGET_CMD=Import-Export $(MAKE) build
|
||||
|
||||
build-nogui: gofiles
|
||||
go build ${BUILD_FLAGS} -o ${EXE_NAME} cmd/${TARGET_CMD}/main.go
|
||||
|
||||
build-ie-nogui:
|
||||
TARGET_CMD=Import-Export $(MAKE) build-nogui
|
||||
|
||||
ifeq "${GOOS}" "windows"
|
||||
PRERESOURCECMD:=cp ./resource.syso ./cmd/launcher/resource.syso
|
||||
POSTRESOURCECMD:=rm -f ./cmd/launcher/resource.syso
|
||||
@ -100,9 +77,6 @@ build-launcher: ${RESOURCE_FILE}
|
||||
go build ${BUILD_FLAGS_LAUNCHER} -o launcher-${EXE} ./cmd/launcher/
|
||||
${POSTRESOURCECMD}
|
||||
|
||||
build-launcher-ie:
|
||||
TARGET_CMD=Import-Export $(MAKE) build-launcher
|
||||
|
||||
versioner:
|
||||
go build ${BUILD_FLAGS} -o versioner utils/versioner/main.go
|
||||
|
||||
@ -114,7 +88,7 @@ ${TGZ_TARGET}: ${DEPLOY_DIR}/${TARGET_OS}
|
||||
cd ${DEPLOY_DIR}/${TARGET_OS} && tar czf ../../../../$@ .
|
||||
|
||||
${DEPLOY_DIR}/linux: ${EXE_TARGET}
|
||||
cp -pf ./internal/frontend/share/icons/${SRC_SVG} ${DEPLOY_DIR}/linux/logo.svg
|
||||
cp -pf ./internal/frontend/share/${SRC_SVG} ${DEPLOY_DIR}/linux/logo.svg
|
||||
cp -pf ./LICENSE ${DEPLOY_DIR}/linux/
|
||||
cp -pf ./Changelog.md ${DEPLOY_DIR}/linux/
|
||||
cp -pf ./dist/${EXE_NAME}.desktop ${DEPLOY_DIR}/linux/
|
||||
@ -124,7 +98,7 @@ ${DEPLOY_DIR}/darwin: ${EXE_TARGET}
|
||||
mv ${EXE_TARGET}/Contents/MacOS/{${DIRNAME},${EXE_NAME}}; \
|
||||
perl -i -pe"s/>${DIRNAME}/>${EXE_NAME}/g" ${EXE_TARGET}/Contents/Info.plist; \
|
||||
fi
|
||||
cp ./internal/frontend/share/icons/${SRC_ICNS} ${DARWINAPP_CONTENTS}/Resources/${TGT_ICNS}
|
||||
cp ./internal/frontend/share/${SRC_ICNS} ${DARWINAPP_CONTENTS}/Resources/${SRC_ICNS}
|
||||
cp LICENSE ${DARWINAPP_CONTENTS}/Resources/
|
||||
rm -rf "${DARWINAPP_CONTENTS}/Frameworks/QtWebEngine.framework"
|
||||
rm -rf "${DARWINAPP_CONTENTS}/Frameworks/QtWebView.framework"
|
||||
@ -132,7 +106,7 @@ ${DEPLOY_DIR}/darwin: ${EXE_TARGET}
|
||||
./utils/remove_non_relative_links_darwin.sh "${EXE_TARGET}${EXE_BINARY_DARWIN}"
|
||||
|
||||
${DEPLOY_DIR}/windows: ${EXE_TARGET}
|
||||
cp ./internal/frontend/share/icons/${SRC_ICO} ${DEPLOY_DIR}/windows/logo.ico
|
||||
cp ./internal/frontend/share/${SRC_ICO} ${DEPLOY_DIR}/windows/logo.ico
|
||||
cp LICENSE ${DEPLOY_DIR}/windows/
|
||||
|
||||
QT_BUILD_TARGET:=build desktop
|
||||
@ -153,9 +127,9 @@ ${EXE_TARGET}: check-has-go gofiles ${RESOURCE_FILE} ${VENDOR_TARGET}
|
||||
|
||||
WINDRES_YEAR:=$(shell date +%Y)
|
||||
APP_VERSION_COMMA:=$(shell echo "${APP_VERSION}" | sed -e 's/[^0-9,.]*//g' -e 's/\./,/g')
|
||||
resource.syso: ./internal/frontend/share/info.rc ./internal/frontend/share/icons/${SRC_ICO} .FORCE
|
||||
resource.syso: ./internal/frontend/share/info.rc ./internal/frontend/share/${SRC_ICO} .FORCE
|
||||
rm -f ./*.syso
|
||||
windres --target=pe-x86-64 -I ./internal/frontend/share/icons/ -D ${WINDRES_DEFINE} -D ICO_FILE=${SRC_ICO} -D EXE_NAME="${EXE_NAME}" -D FILE_VERSION="${APP_VERSION}" -D ORIGINAL_FILE_NAME="${EXE}" -D PRODUCT_VERSION="${APP_VERSION}" -D FILE_VERSION_COMMA=${APP_VERSION_COMMA} -D YEAR=${WINDRES_YEAR} -o $@ $<
|
||||
windres --target=pe-x86-64 -I ./internal/frontend/share/ -D ICO_FILE=${SRC_ICO} -D EXE_NAME="${EXE_NAME}" -D FILE_VERSION="${APP_VERSION}" -D ORIGINAL_FILE_NAME="${EXE}" -D PRODUCT_VERSION="${APP_VERSION}" -D FILE_VERSION_COMMA=${APP_VERSION_COMMA} -D YEAR=${WINDRES_YEAR} -o $@ $<
|
||||
|
||||
## Rules for therecipe/qt
|
||||
.PHONY: prepare-vendor update-vendor update-qt-docs
|
||||
@ -230,16 +204,13 @@ test: gofiles
|
||||
./internal/cookies/... \
|
||||
./internal/crash/... \
|
||||
./internal/events/... \
|
||||
./internal/frontend/autoconfig/... \
|
||||
./internal/frontend/cli/... \
|
||||
./internal/imap/... \
|
||||
./internal/importexport/... \
|
||||
./internal/locations/... \
|
||||
./internal/logging/... \
|
||||
./internal/metrics/... \
|
||||
./internal/smtp/... \
|
||||
./internal/store/... \
|
||||
./internal/transfer/... \
|
||||
./internal/updater/... \
|
||||
./internal/users/... \
|
||||
./internal/versioner/... \
|
||||
@ -259,8 +230,7 @@ integration-test-bridge:
|
||||
mocks:
|
||||
mockgen --package mocks github.com/ProtonMail/proton-bridge/internal/users Locator,PanicHandler,CredentialsStorer,StoreMaker > internal/users/mocks/mocks.go
|
||||
mockgen --package mocks github.com/ProtonMail/proton-bridge/pkg/listener Listener > internal/users/mocks/listener_mocks.go
|
||||
mockgen --package mocks github.com/ProtonMail/proton-bridge/internal/transfer PanicHandler,IMAPClientProvider > internal/transfer/mocks/mocks.go
|
||||
mockgen --package mocks github.com/ProtonMail/proton-bridge/internal/store PanicHandler,BridgeUser,ChangeNotifier > internal/store/mocks/mocks.go
|
||||
mockgen --package mocks github.com/ProtonMail/proton-bridge/internal/store PanicHandler,BridgeUser,ChangeNotifier,Storer > internal/store/mocks/mocks.go
|
||||
mockgen --package mocks github.com/ProtonMail/proton-bridge/pkg/listener Listener > internal/store/mocks/utils_mocks.go
|
||||
mockgen --package mocks github.com/ProtonMail/proton-bridge/pkg/pmapi Client,Manager > pkg/pmapi/mocks/mocks.go
|
||||
mockgen --package mocks github.com/ProtonMail/proton-bridge/pkg/message Fetcher > pkg/message/mocks/mocks.go
|
||||
@ -285,7 +255,7 @@ updates: install-go-mod-outdated
|
||||
doc:
|
||||
godoc -http=:6060
|
||||
|
||||
release-notes: release-notes/bridge_stable.html release-notes/bridge_early.html release-notes/ie_stable.html release-notes/ie_early.html
|
||||
release-notes: release-notes/bridge_stable.html release-notes/bridge_early.html
|
||||
|
||||
release-notes/%.html: release-notes/%.md
|
||||
./utils/release_notes.sh $^
|
||||
@ -293,26 +263,22 @@ release-notes/%.html: release-notes/%.md
|
||||
.PHONY: gofiles
|
||||
# Following files are for the whole app so it makes sense to have them in bridge package.
|
||||
# (Options like cmd or internal were considered and bridge package is the best place for them.)
|
||||
gofiles: ./internal/bridge/credits.go ./internal/importexport/credits.go
|
||||
gofiles: ./internal/bridge/credits.go
|
||||
./internal/bridge/credits.go: ./utils/credits.sh go.mod
|
||||
cd ./utils/ && ./credits.sh bridge
|
||||
./internal/importexport/credits.go: ./utils/credits.sh go.mod
|
||||
cd ./utils/ && ./credits.sh importexport
|
||||
|
||||
|
||||
## Run and debug
|
||||
.PHONY: run run-qt run-qt-cli run-nogui run-nogui-cli run-debug run-qml-preview run-ie-qml-preview run-ie run-ie-qt run-ie-qt-cli run-ie-nogui run-ie-nogui-cli clean-vendor clean-frontend-qt clean-frontend-qt-ie clean-frontend-qt-common clean
|
||||
.PHONY: run run-qt run-qt-cli run-nogui run-nogui-cli run-debug run-qml-preview clean-vendor clean-frontend-qt clean-frontend-qt-common clean
|
||||
|
||||
LOG?=debug
|
||||
LOG_IMAP?=client # client/server/all, or empty to turn it off
|
||||
LOG_SMTP?=--log-smtp # empty to turn it off
|
||||
RUN_FLAGS?=-m -l=${LOG} --log-imap=${LOG_IMAP} ${LOG_SMTP}
|
||||
RUN_FLAGS_IE?=-m -l=${LOG}
|
||||
|
||||
run: run-nogui-cli
|
||||
|
||||
run-qt: ${EXE_TARGET}
|
||||
PROTONMAIL_ENV=dev ./$< ${RUN_FLAGS} | tee last.log
|
||||
PROTONMAIL_ENV=dev ./$< ${RUN_FLAGS} 2>&1 | tee last.log
|
||||
run-qt-cli: ${EXE_TARGET}
|
||||
PROTONMAIL_ENV=dev ./$< ${RUN_FLAGS} -c
|
||||
|
||||
@ -322,28 +288,17 @@ run-nogui-cli: clean-vendor gofiles
|
||||
PROTONMAIL_ENV=dev go run ${BUILD_FLAGS} cmd/${TARGET_CMD}/main.go ${RUN_FLAGS} -c
|
||||
|
||||
run-debug:
|
||||
PROTONMAIL_ENV=dev dlv debug --build-flags "${BUILD_FLAGS}" cmd/${TARGET_CMD}/main.go -- ${RUN_FLAGS}
|
||||
PROTONMAIL_ENV=dev dlv debug --build-flags "${BUILD_FLAGS}" cmd/${TARGET_CMD}/main.go -- ${RUN_FLAGS} --noninteractive
|
||||
|
||||
run-qml-preview:
|
||||
$(MAKE) -C internal/frontend/qt -f Makefile.local qmlpreview
|
||||
run-ie-qml-preview:
|
||||
$(MAKE) -C internal/frontend/qt-ie -f Makefile.local qmlpreview
|
||||
find internal/frontend/qml/ -iname '*qmlc' | xargs rm -f
|
||||
bridge_preview internal/frontend/qml/Bridge_test.qml
|
||||
|
||||
run-ie:
|
||||
TARGET_CMD=Import-Export RUN_FLAGS="${RUN_FLAGS_IE}" $(MAKE) run
|
||||
run-ie-qt:
|
||||
TARGET_CMD=Import-Export RUN_FLAGS="${RUN_FLAGS_IE}" $(MAKE) run-qt
|
||||
run-ie-nogui:
|
||||
TARGET_CMD=Import-Export RUN_FLAGS="${RUN_FLAGS_IE}" $(MAKE) run-nogui
|
||||
|
||||
clean-frontend-qt:
|
||||
$(MAKE) -C internal/frontend/qt -f Makefile.local clean
|
||||
clean-frontend-qt-ie:
|
||||
$(MAKE) -C internal/frontend/qt-ie -f Makefile.local clean
|
||||
clean-frontend-qt-common:
|
||||
$(MAKE) -C internal/frontend/qt-common -f Makefile.local clean
|
||||
$(MAKE) -C internal/frontend -f Makefile.local clean
|
||||
|
||||
clean-vendor: clean-frontend-qt clean-frontend-qt-ie clean-frontend-qt-common
|
||||
clean-vendor: clean-frontend-qt clean-frontend-qt-common
|
||||
rm -rf ./vendor
|
||||
|
||||
clean: clean-vendor
|
||||
|
||||
@ -37,6 +37,8 @@ check the results.
|
||||
|
||||
More details [on the public website](https://protonmail.com/import-export).
|
||||
|
||||
The Import-Export app is developed in separate branch `master-ie`.
|
||||
|
||||
## Launchers
|
||||
Launchers are binaries used to run the ProtonMail Bridge or Import-Export apps.
|
||||
|
||||
@ -69,7 +71,6 @@ or
|
||||
|
||||
### Integration testing
|
||||
- `TEST_ENV`: set which env to use (fake or live)
|
||||
- `TEST_APP`: set which app to test (bridge or ie)
|
||||
- `TEST_ACCOUNTS`: set JSON file with configured accounts
|
||||
- `TAGS`: set build tags for tests
|
||||
- `FEATURES`: set feature dir, file or scenario to test
|
||||
|
||||
1
TODO.md
Normal file
1
TODO.md
Normal file
@ -0,0 +1 @@
|
||||
- when cache is full, we need to stop the watcher? don't want to keep downloading messages and throwing them away when we try to cache them.
|
||||
@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2021 Proton Technologies AG
|
||||
// Copyright (c) 2022 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
|
||||
@ -1,57 +0,0 @@
|
||||
// Copyright (c) 2021 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
// ProtonMail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// ProtonMail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/internal/app/base"
|
||||
"github.com/ProtonMail/proton-bridge/internal/app/ie"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const (
|
||||
appName = "ProtonMail Import-Export app"
|
||||
appUsage = "Import and export messages to/from your ProtonMail account"
|
||||
configName = "importExport"
|
||||
updateURLName = "ie"
|
||||
keychainName = "import-export-app"
|
||||
cacheVersion = "c11"
|
||||
)
|
||||
|
||||
func main() {
|
||||
base, err := base.New(
|
||||
appName,
|
||||
appUsage,
|
||||
configName,
|
||||
updateURLName,
|
||||
keychainName,
|
||||
cacheVersion,
|
||||
)
|
||||
if err != nil {
|
||||
logrus.WithError(err).Fatal("Failed to create app base")
|
||||
}
|
||||
// Other instance already running.
|
||||
if base == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := ie.New(base).Run(os.Args); err != nil {
|
||||
logrus.WithError(err).Fatal("IE exited with error")
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2021 Proton Technologies AG
|
||||
// Copyright (c) 2022 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
@ -24,6 +24,7 @@ import (
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
|
||||
"github.com/Masterminds/semver/v3"
|
||||
"github.com/ProtonMail/gopenpgp/v2/crypto"
|
||||
"github.com/ProtonMail/proton-bridge/internal/config/useragent"
|
||||
"github.com/ProtonMail/proton-bridge/internal/constants"
|
||||
@ -37,11 +38,10 @@ import (
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const appName = "ProtonMail Launcher"
|
||||
|
||||
var (
|
||||
ConfigName = "" // nolint[gochecknoglobals]
|
||||
ExeName = "" // nolint[gochecknoglobals]
|
||||
const (
|
||||
appName = "ProtonMail Launcher"
|
||||
configName = "bridge"
|
||||
exeName = "proton-bridge"
|
||||
)
|
||||
|
||||
func main() { // nolint[funlen]
|
||||
@ -50,12 +50,12 @@ func main() { // nolint[funlen]
|
||||
crashHandler := crash.NewHandler(reporter.ReportException)
|
||||
defer crashHandler.HandlePanic()
|
||||
|
||||
locationsProvider, err := locations.NewDefaultProvider(filepath.Join(constants.VendorName, ConfigName))
|
||||
locationsProvider, err := locations.NewDefaultProvider(filepath.Join(constants.VendorName, configName))
|
||||
if err != nil {
|
||||
logrus.WithError(err).Fatal("Failed to get locations provider")
|
||||
}
|
||||
|
||||
locations := locations.New(locationsProvider, ConfigName)
|
||||
locations := locations.New(locationsProvider, configName)
|
||||
|
||||
logsPath, err := locations.ProvideLogsPath()
|
||||
if err != nil {
|
||||
@ -86,9 +86,9 @@ func main() { // nolint[funlen]
|
||||
|
||||
versioner := versioner.New(updatesPath)
|
||||
|
||||
exe, err := getPathToExecutable(ExeName, versioner, kr, reporter)
|
||||
exe, err := getPathToUpdatedExecutable(exeName, versioner, kr, reporter)
|
||||
if err != nil {
|
||||
if exe, err = getFallbackExecutable(ExeName, versioner); err != nil {
|
||||
if exe, err = getFallbackExecutable(exeName, versioner); err != nil {
|
||||
logrus.WithError(err).Fatal("Failed to find any launchable executable")
|
||||
}
|
||||
}
|
||||
@ -142,7 +142,7 @@ func appendLauncherPath(path string, args []string) []string {
|
||||
return res
|
||||
}
|
||||
|
||||
func getPathToExecutable(
|
||||
func getPathToUpdatedExecutable(
|
||||
name string,
|
||||
versioner *versioner.Versioner,
|
||||
kr *crypto.KeyRing,
|
||||
@ -153,6 +153,11 @@ func getPathToExecutable(
|
||||
return "", errors.Wrap(err, "failed to list available versions")
|
||||
}
|
||||
|
||||
currentVersion, err := semver.StrictNewVersion(constants.Version)
|
||||
if err != nil {
|
||||
logrus.WithField("version", constants.Version).WithError(err).Error("Failed to parse current version")
|
||||
}
|
||||
|
||||
for _, version := range versions {
|
||||
vlog := logrus.WithField("version", version)
|
||||
|
||||
@ -170,6 +175,11 @@ func getPathToExecutable(
|
||||
continue
|
||||
}
|
||||
|
||||
// Skip versions that are less or equal to launcher version.
|
||||
if currentVersion != nil && !version.SemVer().GreaterThan(currentVersion) {
|
||||
continue
|
||||
}
|
||||
|
||||
exe, err := version.GetExecutable(name)
|
||||
if err != nil {
|
||||
vlog.WithError(err).Error("Failed to get executable")
|
||||
@ -179,7 +189,7 @@ func getPathToExecutable(
|
||||
return exe, nil
|
||||
}
|
||||
|
||||
return "", errors.New("no available versions")
|
||||
return "", errors.New("no available newer versions")
|
||||
}
|
||||
|
||||
func getFallbackExecutable(name string, versioner *versioner.Versioner) (string, error) {
|
||||
|
||||
11
dist/proton-ie.desktop
vendored
11
dist/proton-ie.desktop
vendored
@ -1,11 +0,0 @@
|
||||
[Desktop Entry]
|
||||
Type=Application
|
||||
Version=1.1
|
||||
Name=ProtonMail Import-Export app
|
||||
GenericName=ProtonMail Import-Export app for Linux
|
||||
Comment=The Import-Export app helps you to migrate your emails from local files or remote IMAP servers to ProtonMail or simply export emails to local folder.
|
||||
Icon=protonmail-import-export-app
|
||||
Exec=protonmail-import-export-app
|
||||
Terminal=false
|
||||
Categories=Office;Email;Network
|
||||
StartupWMClass=protonmail-import-export-app
|
||||
@ -1,12 +1,12 @@
|
||||
# Encryption
|
||||
|
||||
Encryption is done in PMAPI, bridge utils and bridge itself. The best would be to keep encryption
|
||||
in PMAPI and bridge utils (in pacakge such as messages). All packages are using our high-level
|
||||
GopenPGP library on top of openpgp.
|
||||
in PMAPI and bridge utils (in package such as messages). All packages are using our high-level
|
||||
GopenPGP library on top of OpenPGP.
|
||||
|
||||
## `gopenpgp.KeyRing`
|
||||
|
||||
We use one `KeyRing` per address. Our usage then contains all keys for specific address. Primary
|
||||
key is always on the first position, then there old ones to be able to decrypt last e-mail.
|
||||
Openpgp encrypts given message with all available keys, so we need to first get first (primary)
|
||||
OpenPGP encrypts given message with all available keys, so we need to first get first (primary)
|
||||
key for encryption to have message encrypted only once with primary key.
|
||||
|
||||
@ -1,135 +0,0 @@
|
||||
# Import-Export app
|
||||
|
||||
## Main blocks
|
||||
|
||||
This is basic overview of the main Import-Export blocks.
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
S[ProtonMail server]
|
||||
U[User]
|
||||
|
||||
subgraph "Import-Export app"
|
||||
Users
|
||||
Frontend["Qt / CLI"]
|
||||
ImportExport
|
||||
Transfer
|
||||
|
||||
Frontend --> ImportExport
|
||||
Frontend --> Transfer
|
||||
ImportExport --> Users
|
||||
ImportExport --> Transfer
|
||||
end
|
||||
|
||||
EML --> Transfer
|
||||
MBOX --> Transfer
|
||||
IMAP --> Transfer
|
||||
S --> Transfer
|
||||
|
||||
Transfer --> EML
|
||||
Transfer --> MBOX
|
||||
Transfer --> S
|
||||
|
||||
U --> Frontend
|
||||
```
|
||||
|
||||
## Code structure
|
||||
|
||||
More detailed graph of main types used in Import-Export app and connection between them.
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
PM[ProtonMail Server]
|
||||
EML[EML]
|
||||
MBOX[MBOX]
|
||||
IMAP[IMAP]
|
||||
|
||||
subgraph "Import-Export app"
|
||||
subgraph "pkg users"
|
||||
subgraph "pkg credentials"
|
||||
CredStore[Store]
|
||||
Creds[Credentials]
|
||||
|
||||
CredStore --> Creds
|
||||
end
|
||||
|
||||
US[Users]
|
||||
U[User]
|
||||
|
||||
US --> U
|
||||
end
|
||||
|
||||
subgraph "pkg frontend"
|
||||
CLI
|
||||
Qt
|
||||
end
|
||||
|
||||
subgraph "pkg importExport"
|
||||
IE[ImportExport]
|
||||
end
|
||||
|
||||
subgraph "pkg transfer"
|
||||
Transfer
|
||||
Rules
|
||||
Progress
|
||||
|
||||
Provider
|
||||
LocalProvider
|
||||
EMLProvider
|
||||
MBOXProvider
|
||||
IMAPProvider
|
||||
PMAPIProvider
|
||||
|
||||
Mailbox
|
||||
Message
|
||||
|
||||
Transfer --> |source|Provider
|
||||
Transfer --> |target|Provider
|
||||
Transfer --> Rules
|
||||
Transfer --> Progress
|
||||
|
||||
Provider --> LocalProvider
|
||||
Provider --> EMLProvider
|
||||
Provider --> MBOXProvider
|
||||
Provider --> IMAPProvider
|
||||
Provider --> PMAPIProvider
|
||||
|
||||
LocalProvider --> EMLProvider
|
||||
LocalProvider --> MBOXProvider
|
||||
|
||||
Provider --> Mailbox
|
||||
Provider --> Message
|
||||
|
||||
end
|
||||
|
||||
subgraph PMAPI
|
||||
APIM[ClientManager]
|
||||
APIC[Client]
|
||||
|
||||
APIM --> APIC
|
||||
end
|
||||
end
|
||||
|
||||
CLI --> IE
|
||||
CLI --> Transfer
|
||||
CLI --> Progress
|
||||
Qt --> IE
|
||||
Qt --> Transfer
|
||||
Qt --> Progress
|
||||
|
||||
U --> CredStore
|
||||
U --> Creds
|
||||
|
||||
US --> APIM
|
||||
U --> APIM
|
||||
|
||||
PMAPIProvider --> APIM
|
||||
EMLProvider --> EML
|
||||
MBOXProvider --> MBOX
|
||||
IMAPProvider --> IMAP
|
||||
|
||||
IE --> US
|
||||
IE --> Transfer
|
||||
|
||||
APIC --> PM
|
||||
```
|
||||
@ -1,14 +1,9 @@
|
||||
# Documentation
|
||||
# Bridge Documentation
|
||||
|
||||
Documentation pages in order to read for a novice:
|
||||
|
||||
## Bridge
|
||||
|
||||
* [Bridge code](bridge.md)
|
||||
* [Internal Bridge database](database.md)
|
||||
* [Communication between Bridge, Client and Server](communication.md)
|
||||
* [Encryption](encryption.md)
|
||||
|
||||
## Import-Export app
|
||||
|
||||
* [Import-Export code](importexport.md)
|
||||
|
||||
40
go.mod
40
go.mod
@ -1,37 +1,39 @@
|
||||
module github.com/ProtonMail/proton-bridge
|
||||
|
||||
go 1.13
|
||||
go 1.15
|
||||
|
||||
// These dependencies are `replace`d below, so the version numbers should be ignored.
|
||||
// They are in a separate require block to highlight this.
|
||||
require (
|
||||
github.com/docker/docker-credential-helpers v0.6.3
|
||||
github.com/emersion/go-imap v1.0.6
|
||||
github.com/jameskeane/bcrypt v0.0.0-20170924085257-7509ea014998
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9
|
||||
github.com/jameskeane/bcrypt v0.0.0-20170924085257-7509ea014998 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/0xAX/notificator v0.0.0-20191016112426-3962a5ea8da1
|
||||
github.com/Masterminds/semver/v3 v3.1.0
|
||||
github.com/ProtonMail/go-autostart v0.0.0-20181114175602-c5272053443a
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20211221144345-a4f6767435ab
|
||||
github.com/ProtonMail/go-imap-id v0.0.0-20190926060100-f94a56b9ecde
|
||||
github.com/ProtonMail/go-rfc5322 v0.5.0
|
||||
github.com/ProtonMail/go-rfc5322 v0.8.0
|
||||
github.com/ProtonMail/go-srp v0.0.1
|
||||
github.com/ProtonMail/go-vcard v0.0.0-20180326232728-33aaa0a0c8a5
|
||||
github.com/ProtonMail/gopenpgp/v2 v2.1.3
|
||||
github.com/ProtonMail/gopenpgp/v2 v2.4.1
|
||||
github.com/PuerkitoBio/goquery v1.5.1
|
||||
github.com/abiosoft/ishell v2.0.0+incompatible
|
||||
github.com/abiosoft/readline v0.0.0-20180607040430-155bce2042db // indirect
|
||||
github.com/allan-simon/go-singleinstance v0.0.0-20160830203053-79edcfdc2dfc
|
||||
github.com/chzyer/logex v1.1.10 // indirect
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 // indirect
|
||||
github.com/cucumber/godog v0.8.1
|
||||
github.com/cucumber/godog v0.12.1
|
||||
github.com/cucumber/messages-go/v16 v16.0.1
|
||||
github.com/elastic/go-sysinfo v1.7.1
|
||||
github.com/elastic/go-windows v1.0.1 // indirect
|
||||
github.com/emersion/go-imap-appendlimit v0.0.0-20190308131241-25671c986a6a
|
||||
github.com/emersion/go-imap-idle v0.0.0-20200601154248-f05f54664cc4
|
||||
github.com/emersion/go-imap-move v0.0.0-20190710073258-6e5a51a5b342
|
||||
github.com/emersion/go-imap-quota v0.0.0-20210203125329-619074823f3c
|
||||
github.com/emersion/go-imap-unselect v0.0.0-20171113212723-b985794e5f26
|
||||
github.com/emersion/go-mbox v1.0.2
|
||||
github.com/emersion/go-message v0.12.1-0.20201221184100-40c3f864532b
|
||||
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21
|
||||
github.com/emersion/go-smtp v0.14.0
|
||||
@ -39,12 +41,11 @@ require (
|
||||
github.com/emersion/go-vcard v0.0.0-20190105225839-8856043f13c5 // indirect
|
||||
github.com/fatih/color v1.9.0
|
||||
github.com/flynn-archive/go-shlex v0.0.0-20150515145356-3f9db97f8568 // indirect
|
||||
github.com/getsentry/sentry-go v0.8.0
|
||||
github.com/getsentry/sentry-go v0.12.0
|
||||
github.com/go-resty/resty/v2 v2.6.0
|
||||
github.com/golang/mock v1.4.4
|
||||
github.com/google/go-cmp v0.5.1
|
||||
github.com/google/go-cmp v0.5.5
|
||||
github.com/google/uuid v1.1.1
|
||||
github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.0
|
||||
github.com/jaytaylor/html2text v0.0.0-20200412013138-3577fbdbcff7
|
||||
github.com/keybase/go-keychain v0.0.0-20200502122510-cda31fe0c86d
|
||||
@ -54,23 +55,26 @@ require (
|
||||
github.com/nsf/jsondiff v0.0.0-20200515183724-f29ed568f4ce
|
||||
github.com/olekukonko/tablewriter v0.0.4 // indirect
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/prometheus/procfs v0.7.3 // indirect
|
||||
github.com/ricochet2200/go-disk-usage/du v0.0.0-20210707232629-ac9918953285
|
||||
github.com/sirupsen/logrus v1.7.0
|
||||
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966
|
||||
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect
|
||||
github.com/stretchr/testify v1.6.1
|
||||
github.com/stretchr/testify v1.7.0
|
||||
github.com/therecipe/qt v0.0.0-20200701200531-7f61353ee73e
|
||||
github.com/therecipe/qt/internal/binding/files/docs/5.12.0 v0.0.0-20200904063919-c0c124a5770d // indirect
|
||||
github.com/therecipe/qt/internal/binding/files/docs/5.13.0 v0.0.0-20200904063919-c0c124a5770d // indirect
|
||||
github.com/urfave/cli/v2 v2.2.0
|
||||
github.com/vmihailenco/msgpack/v5 v5.1.3
|
||||
go.etcd.io/bbolt v1.3.5
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4
|
||||
golang.org/x/text v0.3.5-0.20201125200606-c27b9fd57aec
|
||||
go.etcd.io/bbolt v1.3.6
|
||||
golang.org/x/net v0.0.0-20211008194852-3b03d305991f
|
||||
golang.org/x/sys v0.0.0-20220111092808-5a964db01320
|
||||
golang.org/x/text v0.3.7
|
||||
howett.net/plist v1.0.0 // indirect
|
||||
)
|
||||
|
||||
replace (
|
||||
github.com/docker/docker-credential-helpers => github.com/ProtonMail/docker-credential-helpers v1.1.0
|
||||
github.com/emersion/go-imap => github.com/ProtonMail/go-imap v0.0.0-20201228133358-4db68cea0cac
|
||||
github.com/jameskeane/bcrypt => github.com/ProtonMail/bcrypt v0.0.0-20170924085257-7509ea014998
|
||||
golang.org/x/crypto => github.com/ProtonMail/crypto v0.0.0-20201112115411-41db4ea0dd1c
|
||||
github.com/emersion/go-message => github.com/ProtonMail/go-message v0.0.0-20210611055058-fabeff2ec753
|
||||
github.com/jameskeane/bcrypt => github.com/ProtonMail/bcrypt v0.0.0-20210511135022-227b4adcab57
|
||||
)
|
||||
|
||||
385
go.sum
385
go.sum
@ -1,3 +1,16 @@
|
||||
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=
|
||||
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
|
||||
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
||||
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
|
||||
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
|
||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||
cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
|
||||
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=
|
||||
github.com/0xAX/notificator v0.0.0-20191016112426-3962a5ea8da1 h1:j9HaafapDbPbGRDku6e/HRs6KBMcKHiWcm1/9Sbxnl4=
|
||||
github.com/0xAX/notificator v0.0.0-20191016112426-3962a5ea8da1/go.mod h1:NtXa9WwQsukMHZpjNakTTz0LArxvGYdPA9CjIcUSZ6s=
|
||||
github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8=
|
||||
@ -8,28 +21,32 @@ github.com/CloudyKit/jet/v3 v3.0.0/go.mod h1:HKQPgSJmdK8hdoAbKUUWajkHyHo4RaU5rMd
|
||||
github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY=
|
||||
github.com/Masterminds/semver/v3 v3.1.0 h1:Y2lUDsFKVRSYGojLJ1yLxSXdMmMYTYls0rCvoqmMUQk=
|
||||
github.com/Masterminds/semver/v3 v3.1.0/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
|
||||
github.com/ProtonMail/bcrypt v0.0.0-20170924085257-7509ea014998 h1:YT2uVwQiRQZxCaaahwfcgTq2j3j66w00n/27gb/zubs=
|
||||
github.com/ProtonMail/bcrypt v0.0.0-20170924085257-7509ea014998/go.mod h1:HecWFHognK8GfRDGnFQbW/LiV7A3MX3gZVs45vk5h8I=
|
||||
github.com/ProtonMail/crypto v0.0.0-20201112115411-41db4ea0dd1c h1:iaVbEOnskSGgcH7XQWHG6VPirHDRoYe+Idd0/dl4m8A=
|
||||
github.com/ProtonMail/crypto v0.0.0-20201112115411-41db4ea0dd1c/go.mod h1:Pxr7w4gA2ikI4sWyYwEffm+oew1WAJHzG1SiDpQMkrI=
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/ProtonMail/bcrypt v0.0.0-20210511135022-227b4adcab57 h1:pHA4K54ifoogVLunGGHi3xyF5Nz4x+Uh3dJuy3NwGQQ=
|
||||
github.com/ProtonMail/bcrypt v0.0.0-20210511135022-227b4adcab57/go.mod h1:HecWFHognK8GfRDGnFQbW/LiV7A3MX3gZVs45vk5h8I=
|
||||
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/go-autostart v0.0.0-20181114175602-c5272053443a h1:fXK2KsfnkBV9Nh+9SKzHchYjuE9s0vI20JG1mbtEAcc=
|
||||
github.com/ProtonMail/go-autostart v0.0.0-20181114175602-c5272053443a/go.mod h1:oTGdE7/DlWIr23G0IKW3OXK9wZ5Hw1GGiaJFccTvZi4=
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20201208171014-cdb7591792e2 h1:pQkjJELHayW59jp7r4G5Dlmnicr5McejDfwsjcwI1SU=
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20201208171014-cdb7591792e2/go.mod h1:HTM9X7e9oLwn7RiqLG0UVwVRJenLs3wN+tQ0NPAfwMQ=
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo=
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20211221144345-a4f6767435ab h1:5FiL/TCaiKCss/BLMIACDxxadYrx767l9kh0qYX+sLQ=
|
||||
github.com/ProtonMail/go-crypto v0.0.0-20211221144345-a4f6767435ab/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo=
|
||||
github.com/ProtonMail/go-imap v0.0.0-20201228133358-4db68cea0cac h1:2xU3QncAiS/W3UlWZTkbNKW5WkLzk6Egl1T0xX+sbjs=
|
||||
github.com/ProtonMail/go-imap v0.0.0-20201228133358-4db68cea0cac/go.mod h1:yKASt+C3ZiDAiCSssxg9caIckWF/JG7ZQTO7GAmvicU=
|
||||
github.com/ProtonMail/go-imap-id v0.0.0-20190926060100-f94a56b9ecde h1:5koQozTDELymYOyFbQ/VSubexAEXzDR8qGM5mO8GRdw=
|
||||
github.com/ProtonMail/go-imap-id v0.0.0-20190926060100-f94a56b9ecde/go.mod h1:795VPXcRUIQ9JyMNHP4el582VokQfippgjkQP3Gk0r0=
|
||||
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-20190923161245-9b5a4261663a h1:W6RrgN/sTxg1msqzFFb+G80MFmpjMw61IU+slm+wln4=
|
||||
github.com/ProtonMail/go-mime v0.0.0-20190923161245-9b5a4261663a/go.mod h1:NYt+V3/4rEeDuaev/zw1zCq8uqVEuPHzDPo3OZrlGJ4=
|
||||
github.com/ProtonMail/go-rfc5322 v0.5.0 h1:LbKWjgfvumYZCr8BgGyTUk3ETGkFLAjQdkuSUpZ5CcE=
|
||||
github.com/ProtonMail/go-rfc5322 v0.5.0/go.mod h1:mzZWlMWnQJuYLL7JpzuPF5+FimV2lZ9f0jeq24kJjpU=
|
||||
github.com/ProtonMail/go-rfc5322 v0.8.0 h1:7emrf75n3CDIduQflx7aT1nJa5h/kGsiFKUYX/+IAkU=
|
||||
github.com/ProtonMail/go-rfc5322 v0.8.0/go.mod h1:BwpTbkJxkMGkc+pC84AXZnwuWOisEULBpfPIyIKS/Us=
|
||||
github.com/ProtonMail/go-srp v0.0.1 h1:J0O9Zb5XTC6iDrB7feH41cu+TUEB+l7uHctXIK6oS2o=
|
||||
github.com/ProtonMail/go-srp v0.0.1/go.mod h1:Uvv5cqSGCs8MTZ8sbKiCkBnaB6/OA3eq2mc77tl2VVA=
|
||||
github.com/ProtonMail/go-vcard v0.0.0-20180326232728-33aaa0a0c8a5 h1:Uga1DHFN4GUxuDQr0F71tpi8I9HqPIlZodZAI1lR6VQ=
|
||||
github.com/ProtonMail/go-vcard v0.0.0-20180326232728-33aaa0a0c8a5/go.mod h1:oeP9CMN+ajWp5jKp1kue5daJNwMMxLF+ujPaUIoJWlA=
|
||||
github.com/ProtonMail/gopenpgp/v2 v2.1.3 h1:4+nFDJ9WtcUQTip/je2Ll3P21XhAUl4asWsafLrw97c=
|
||||
github.com/ProtonMail/gopenpgp/v2 v2.1.3/go.mod h1:WeYndoqEcRR4/QbgRL24z6OwYX5T1RWerRk8NfZ6rJM=
|
||||
github.com/ProtonMail/gopenpgp/v2 v2.4.1 h1:b3El0zabaKi73u4sRnb3hOOUczuKuYpN8wnp7wRsZSc=
|
||||
github.com/ProtonMail/gopenpgp/v2 v2.4.1/go.mod h1:RFjoVjfhV8f78tjz/fLrp/OXkugL3QmWsiJq/fsQYA4=
|
||||
github.com/PuerkitoBio/goquery v1.5.1 h1:PSPBGne8NIUWw+/7vFBV+kG2J/5MOjbzc7154OaKCSE=
|
||||
github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
|
||||
github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398/go.mod h1:a1uqRtAwp2Xwc6WNPJEufxJ7fx3npB4UV/JOLmbu5I0=
|
||||
@ -38,29 +55,53 @@ github.com/abiosoft/ishell v2.0.0+incompatible/go.mod h1:HQR9AqF2R3P4XXpMpI0NAzg
|
||||
github.com/abiosoft/readline v0.0.0-20180607040430-155bce2042db h1:CjPUSXOiYptLbTdr1RceuZgSFDQ7U15ITERUGrUORx8=
|
||||
github.com/abiosoft/readline v0.0.0-20180607040430-155bce2042db/go.mod h1:rB3B4rKii8V21ydCbIzH5hZiCQE7f5E9SzUb/ZZx530=
|
||||
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/allan-simon/go-singleinstance v0.0.0-20160830203053-79edcfdc2dfc h1:mZca0/HZ/XWXP9txkfdl2GH6mUzBqAlyJz3u5Lg8fuA=
|
||||
github.com/allan-simon/go-singleinstance v0.0.0-20160830203053-79edcfdc2dfc/go.mod h1:qqsTQiwdyqxU05iDCsi0oN3P4nrVxAmn8xCtODDSf/U=
|
||||
github.com/andybalholm/cascadia v1.1.0 h1:BuuO6sSfQNFRu1LppgbD25Hr2vLYW25JvxHs5zzsLTo=
|
||||
github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
|
||||
github.com/antlr/antlr4 v0.0.0-20201029161626-9a95f0cc3d7c h1:j/C2kxPfyE0d87/ggAjIsCV5Cdkqmjb+O0W8W+1J+IY=
|
||||
github.com/antlr/antlr4 v0.0.0-20201029161626-9a95f0cc3d7c/go.mod h1:T7PbCXFs94rrTttyxjbyT5+/1V8T2TYDejxUfHJjw1Y=
|
||||
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
|
||||
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
|
||||
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
|
||||
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||
github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
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/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM=
|
||||
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
||||
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
|
||||
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
||||
github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk=
|
||||
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/cucumber/godog v0.8.1 h1:lVb+X41I4YDreE+ibZ50bdXmySxgRviYFgKY6Aw4XE8=
|
||||
github.com/cucumber/godog v0.8.1/go.mod h1:vSh3r/lM+psC1BPXvdkSEuNjmXfpVqrMGYAElF6hxnA=
|
||||
github.com/cronokirby/saferith v0.31.0 h1:TIlhldetKLeGAb19bZvWiuwQEzfzwSPthDEyJ9Ah8xs=
|
||||
github.com/cronokirby/saferith v0.31.0/go.mod h1:QKJhjoqUtBsXCAVEjw38mFqoi7DebT7kthcD7UzbnoA=
|
||||
github.com/cucumber/gherkin-go/v19 v19.0.3 h1:mMSKu1077ffLbTJULUfM5HPokgeBcIGboyeNUof1MdE=
|
||||
github.com/cucumber/gherkin-go/v19 v19.0.3/go.mod h1:jY/NP6jUtRSArQQJ5h1FXOUgk5fZK24qtE7vKi776Vw=
|
||||
github.com/cucumber/godog v0.12.1 h1:IhWVYFKDReM5WsuA9AuRLRPWOyvFNO9UBUKrNfLPais=
|
||||
github.com/cucumber/godog v0.12.1/go.mod h1:u6SD7IXC49dLpPN35kal0oYEjsXZWee4pW6Tm9t5pIc=
|
||||
github.com/cucumber/messages-go/v16 v16.0.0/go.mod h1:EJcyR5Mm5ZuDsKJnT2N9KRnBK30BGjtYotDKpwQ0v6g=
|
||||
github.com/cucumber/messages-go/v16 v16.0.1 h1:fvkpwsLgnIm0qugftrw2YwNlio+ABe2Iu94Ap8GMYIY=
|
||||
github.com/cucumber/messages-go/v16 v16.0.1/go.mod h1:EJcyR5Mm5ZuDsKJnT2N9KRnBK30BGjtYotDKpwQ0v6g=
|
||||
github.com/danieljoos/wincred v1.1.0 h1:3RNcEpBg4IhIChZdFRSdlQt1QjCp1sMAPIrOnm7Yf8g=
|
||||
github.com/danieljoos/wincred v1.1.0/go.mod h1:XYlo+eRTsVA9aHGp7NGjFkPla4m+DCL7hqDjlFjiygg=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
@ -69,35 +110,34 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
|
||||
github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
|
||||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM=
|
||||
github.com/elastic/go-sysinfo v1.7.1 h1:Wx4DSARcKLllpKT2TnFVdSUJOsybqMYCNQZq1/wO+s0=
|
||||
github.com/elastic/go-sysinfo v1.7.1/go.mod h1:i1ZYdU10oLNfRzq4vq62BEwD2fH8KaWh6eh0ikPT9F0=
|
||||
github.com/elastic/go-windows v1.0.0/go.mod h1:TsU0Nrp7/y3+VwE82FoZF8gC/XFg/Elz6CcloAxnPgU=
|
||||
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-appendlimit v0.0.0-20190308131241-25671c986a6a h1:bMdSPm6sssuOFpIaveu3XGAijMS3Tq2S3EqFZmZxidc=
|
||||
github.com/emersion/go-imap-appendlimit v0.0.0-20190308131241-25671c986a6a/go.mod h1:ikgISoP7pRAolqsVP64yMteJa2FIpS6ju88eBT6K1yQ=
|
||||
github.com/emersion/go-imap-idle v0.0.0-20200601154248-f05f54664cc4 h1:/JIALzmCduf5o8TWJSiOBzTb9+R0SChwElUrJLlp2po=
|
||||
github.com/emersion/go-imap-idle v0.0.0-20200601154248-f05f54664cc4/go.mod h1:o14zPKCmEH5WC1vU5SdPoZGgNvQx7zzKSnxPQlobo78=
|
||||
github.com/emersion/go-imap-move v0.0.0-20190710073258-6e5a51a5b342 h1:5p1t3e1PomYgLWwEwhwEU5kVBwcyAcVrOpexv8AeZx0=
|
||||
github.com/emersion/go-imap-move v0.0.0-20190710073258-6e5a51a5b342/go.mod h1:QuMaZcKFDVI0yCrnAbPLfbwllz1wtOrZH8/vZ5yzp4w=
|
||||
github.com/emersion/go-imap-quota v0.0.0-20210203125329-619074823f3c h1:khcEdu1yFiZjBgi7gGnQiLhpSgghJ0YTnKD0l4EUqqc=
|
||||
github.com/emersion/go-imap-quota v0.0.0-20210203125329-619074823f3c/go.mod h1:iApyhIQBiU4XFyr+3kdJyyGqle82TbQyuP2o+OZHrV0=
|
||||
github.com/emersion/go-imap-unselect v0.0.0-20171113212723-b985794e5f26 h1:FiSb8+XBQQSkcX3ubr+1tAtlRJBYaFmRZqOAweZ9Wy8=
|
||||
github.com/emersion/go-imap-unselect v0.0.0-20171113212723-b985794e5f26/go.mod h1:+gnnZx3Mg3MnCzZrv0eZdp5puxXQUgGT/6N6L7ShKfM=
|
||||
github.com/emersion/go-mbox v1.0.2 h1:tE/rT+lEugK9y0myEymCCHnwlZN04hlXPrbKkxRBA5I=
|
||||
github.com/emersion/go-mbox v1.0.2/go.mod h1:Yp9IVuuOYLEuMv4yjgDHvhb5mHOcYH6x92Oas3QqEZI=
|
||||
github.com/emersion/go-message v0.11.1/go.mod h1:C4jnca5HOTo4bGN9YdqNQM9sITuT3Y0K6bSUw9RklvY=
|
||||
github.com/emersion/go-message v0.12.1-0.20201221184100-40c3f864532b h1:xYuhW6egTaCP+zjbUcfoy/Dr3ASdVPR9W7fmkHvZHPE=
|
||||
github.com/emersion/go-message v0.12.1-0.20201221184100-40c3f864532b/go.mod h1:N1JWdZQ2WRUalmdHAX308CWBq747VJ8oUorFI3VCBwU=
|
||||
github.com/emersion/go-sasl v0.0.0-20191210011802-430746ea8b9b/go.mod h1:G/dpzLu16WtQpBfQ/z3LYiYJn3ZhKSGWn83fyoyQe/k=
|
||||
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21 h1:OJyUGMJTzHTd1XQp98QTaHernxMYzRaOasRir9hUlFQ=
|
||||
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
|
||||
github.com/emersion/go-smtp v0.14.0 h1:RYW203p+EcPjL8Z/ZpT9lZ6iOc8MG1MQzEx1UKEkXlA=
|
||||
github.com/emersion/go-smtp v0.14.0/go.mod h1:qm27SGYgoIPRot6ubfQ/GpiPy/g3PaZAVRxiO/sDUgQ=
|
||||
github.com/emersion/go-textwrapper v0.0.0-20160606182133-d0e65e56babe/go.mod h1:aqO8z8wPrjkscevZJFVE1wXJrLpC5LtJG7fqLOsPb2U=
|
||||
github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594 h1:IbFBtwoTQyw0fIM5xv1HF+Y+3ZijDR839WMulgxCcUY=
|
||||
github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594/go.mod h1:aqO8z8wPrjkscevZJFVE1wXJrLpC5LtJG7fqLOsPb2U=
|
||||
github.com/emersion/go-vcard v0.0.0-20190105225839-8856043f13c5 h1:n9qx98xiS5V4x2WIpPC2rr9mUM5ri9r/YhCEKbhCHro=
|
||||
github.com/emersion/go-vcard v0.0.0-20190105225839-8856043f13c5/go.mod h1:WIi9g8OKJQHXtQbx7GExlo6UAFaui9WDMYabJ+Be4WI=
|
||||
github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw=
|
||||
github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s=
|
||||
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
|
||||
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
|
||||
@ -105,42 +145,98 @@ github.com/flynn-archive/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BMXYYRWT
|
||||
github.com/flynn-archive/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:rZfgFAXFS/z/lEd6LJmf9HVZ1LkgYiHx5pHhV5DR16M=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc=
|
||||
github.com/getsentry/sentry-go v0.8.0 h1:F52cjBVLuiTfdW6p4JFuxlt3pOjKfWYT/aka7cdJ7v0=
|
||||
github.com/getsentry/sentry-go v0.8.0/go.mod h1:kELm/9iCblqUYh+ZRML7PNdCvEuw24wBvJPYyi86cws=
|
||||
github.com/getsentry/sentry-go v0.12.0 h1:era7g0re5iY13bHSdN/xMkyV+5zZppjRVQhZrXCaEIk=
|
||||
github.com/getsentry/sentry-go v0.12.0/go.mod h1:NSap0JBYWzHND8oMbyi0+XZhUalc1TBdRL1M71JZW2c=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
|
||||
github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM=
|
||||
github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98=
|
||||
github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w=
|
||||
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
||||
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=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8=
|
||||
github.com/go-resty/resty/v2 v2.6.0 h1:joIR5PNLM2EFqqESUjCMGXrWmXNHEU9CEiK813oKYS4=
|
||||
github.com/go-resty/resty/v2 v2.6.0/go.mod h1:PwvJS6hvaPkjtjNg9ph+VrSD92bi5Zq73w/BIH7cC3Q=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
|
||||
github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
||||
github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
|
||||
github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
|
||||
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
||||
github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc=
|
||||
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/gomodule/redigo v1.7.1-0.20190724094224-574c33c3df38/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.1 h1:JFrFEBb2xKufg6XkJsJr+WbKb4FQlURi5RUcBveYu9k=
|
||||
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
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/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20190411002643-bd77b112433e h1:XWcjeEtTFTOVA9Fs1w7n2XBftk5ib4oZrhzWk0B+3eA=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20190411002643-bd77b112433e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c h1:7lF+Vz0LqiRidnzC1Oq86fpX1q/iEv2KJdrCtttYjT4=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
||||
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
|
||||
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
|
||||
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
||||
github.com/hashicorp/go-immutable-radix v1.3.0 h1:8exGP7ego3OmkfksihtSouGMZ+hQrhxx+FVELeXpVPE=
|
||||
github.com/hashicorp/go-immutable-radix v1.3.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
||||
github.com/hashicorp/go-memdb v1.3.0 h1:xdXq34gBOMEloa9rlGStLxmfX/dyIK8htOv36dQUwHU=
|
||||
github.com/hashicorp/go-memdb v1.3.0/go.mod h1:Mluclgwib3R93Hk5fxEfiRhB+6Dar64wWh71LpNSe3g=
|
||||
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
|
||||
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
|
||||
github.com/hashicorp/go-multierror v1.1.0 h1:B9UzwGQJehnUY1yNrnwREHc3fGbC2xefo8g4TbElacI=
|
||||
github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
|
||||
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
|
||||
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
|
||||
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
|
||||
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE=
|
||||
github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
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/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/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
@ -151,9 +247,16 @@ github.com/iris-contrib/pongo2 v0.0.1/go.mod h1:Ssh+00+3GAZqSQb30AvBRNxBx7rf0Gqw
|
||||
github.com/iris-contrib/schema v0.0.1/go.mod h1:urYA3uvUNG1TIIjOSCzHr9/LmbQo8LrOcOqfqxa4hXw=
|
||||
github.com/jaytaylor/html2text v0.0.0-20200412013138-3577fbdbcff7 h1:g0fAGBisHaEQ0TRq1iBvemFRf+8AEWEmBESSiWB3Vsc=
|
||||
github.com/jaytaylor/html2text v0.0.0-20200412013138-3577fbdbcff7/go.mod h1:CVKlgaMiht+LXvHG173ujK6JUhZXKb2u/BQtjPDIvyk=
|
||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901 h1:rp+c0RAYOWj8l6qbCUTSiRLG/iKnW3K3/QfPPuSsBt4=
|
||||
github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901/go.mod h1:Z86h9688Y0wesXCyonoVr47MasHilkuLMqGhRZ4Hpak=
|
||||
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k=
|
||||
github.com/kataras/golog v0.0.10/go.mod h1:yJ8YKCmyL+nWjERB90Qwn+bdyBZsaQwU3bTVFgkFIp8=
|
||||
github.com/kataras/iris/v12 v12.1.8/go.mod h1:LMYy4VlP67TQ3Zgriz8RE2h2kMZV2SgMYbq3UhfoFmE=
|
||||
@ -163,91 +266,137 @@ github.com/kataras/sitemap v0.0.5/go.mod h1:KY2eugMKiPwsJgx7+U103YZehfvNGOXURubc
|
||||
github.com/keybase/go-keychain v0.0.0-20200502122510-cda31fe0c86d h1:gVjhBCfVGl32RIBooOANzfw+0UqX8HU+yPlMv8vypcg=
|
||||
github.com/keybase/go-keychain v0.0.0-20200502122510-cda31fe0c86d/go.mod h1:W6EbaYmb4RldPn0N3gvVHjY1wmU59kbymhW9NATWhwY=
|
||||
github.com/keybase/go.dbus v0.0.0-20200324223359-a94be52c0b03/go.mod h1:a8clEhrrGV/d76/f9r2I41BwANMihfZYV9C223vaxqE=
|
||||
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/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
||||
github.com/klauspost/compress v1.9.7/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
||||
github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2/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 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/labstack/echo/v4 v4.1.11/go.mod h1:i541M3Fj6f76NZtHSj7TXnyM8n2gaodfvfxNnFqi74g=
|
||||
github.com/labstack/echo/v4 v4.5.0/go.mod h1:czIriw4a0C1dFun+ObrXp7ok03xON0N1awStJ6ArI7Y=
|
||||
github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
|
||||
github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8=
|
||||
github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
|
||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/martinlindhe/base36 v1.0.0/go.mod h1:+AtEs8xrBpCeYgSLoY/aJ6Wf37jtBuR0s35750M27+8=
|
||||
github.com/martinlindhe/base36 v1.1.0 h1:cIwvvwYse/0+1CkUPYH5ZvVIYG3JrILmQEIbLuar02Y=
|
||||
github.com/martinlindhe/base36 v1.1.0/go.mod h1:+AtEs8xrBpCeYgSLoY/aJ6Wf37jtBuR0s35750M27+8=
|
||||
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA=
|
||||
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-colorable v0.1.11 h1:nQ+aFkoE2TMGc0b68U2OKSexC+eq46+XwZzWXHRmPYs=
|
||||
github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
|
||||
github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGeM=
|
||||
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
|
||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/mediocregopher/radix/v3 v3.4.2/go.mod h1:8FL3F6UQRXHXIBSPUs5h0RybMF8i4n7wVopoX3x7Bv8=
|
||||
github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc=
|
||||
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||
github.com/miekg/dns v1.1.41 h1:WMszZWJG0XmzbK9FEmzH2TVcqYzFesusSIB41b8KHxY=
|
||||
github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
|
||||
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
|
||||
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
|
||||
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
|
||||
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
|
||||
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg=
|
||||
github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w=
|
||||
github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
|
||||
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/nsf/jsondiff v0.0.0-20200515183724-f29ed568f4ce h1:RPclfga2SEJmgMmz2k+Mg7cowZ8yv4Trqw9UsJby758=
|
||||
github.com/nsf/jsondiff v0.0.0-20200515183724-f29ed568f4ce/go.mod h1:uFMI8w+ref4v2r9jz+c9i1IfIttS/OkmLfrk1jne5hs=
|
||||
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
|
||||
github.com/olekukonko/tablewriter v0.0.4 h1:vHD/YYe1Wolo78koG299f7V/VAS08c6IpCLn+Ejf/w8=
|
||||
github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
|
||||
github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
|
||||
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/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=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.0-20190425082905-87a4384529e0/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU=
|
||||
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
||||
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
|
||||
github.com/ricochet2200/go-disk-usage/du v0.0.0-20210707232629-ac9918953285 h1:d54EL9l+XteliUfUCGsEwwuk65dmmxX85VXF+9T6+50=
|
||||
github.com/ricochet2200/go-disk-usage/du v0.0.0-20210707232629-ac9918953285/go.mod h1:fxIDly1xtudczrZeOOlfaUvd2OPb2qZAPuWdU2BsBTk=
|
||||
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/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo=
|
||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||
github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||
github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g=
|
||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
|
||||
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.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM=
|
||||
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 h1:JIAuq3EEf9cgbU6AtGPK4CTG3Zf6CKMNqf0MHTggAUA=
|
||||
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
|
||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
|
||||
github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI=
|
||||
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
|
||||
github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
|
||||
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf h1:pvbZ0lM0XWPBqUKqFU8cmavspvIl9nulOYwdy6IFRRo=
|
||||
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=
|
||||
@ -258,15 +407,17 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf
|
||||
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 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||
github.com/therecipe/qt v0.0.0-20200701200531-7f61353ee73e h1:G0DQ/TRQyrEZjtLlLwevFjaRiG8eeCMlq9WXQ2OO2bk=
|
||||
github.com/therecipe/qt v0.0.0-20200701200531-7f61353ee73e/go.mod h1:SUUR2j3aE1z6/g76SdD6NwACEpvCxb3fvG82eKbD6us=
|
||||
github.com/therecipe/qt v0.0.0-20200904063919-c0c124a5770d h1:T+d8FnaLSvM/1BdlDXhW4d5dr2F07bAbB+LpgzMxx+o=
|
||||
github.com/therecipe/qt/internal/binding/files/docs/5.12.0 v0.0.0-20200904063919-c0c124a5770d h1:hAZyEG2swPRWjF0kqqdGERXUazYnRJdAk4a58f14z7Y=
|
||||
github.com/therecipe/qt/internal/binding/files/docs/5.12.0 v0.0.0-20200904063919-c0c124a5770d/go.mod h1:7m8PDYDEtEVqfjoUQc2UrFqhG0CDmoVJjRlQxexndFc=
|
||||
github.com/therecipe/qt/internal/binding/files/docs/5.13.0 v0.0.0-20200904063919-c0c124a5770d h1:AJRoBel/g9cDS+yE8BcN3E+TDD/xNAguG21aoR8DAIE=
|
||||
github.com/therecipe/qt/internal/binding/files/docs/5.13.0 v0.0.0-20200904063919-c0c124a5770d/go.mod h1:mH55Ek7AZcdns5KPp99O0bg+78el64YCYWHiQKrOdt4=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
|
||||
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
||||
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
|
||||
@ -277,6 +428,7 @@ github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKn
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasthttp v1.6.0/go.mod h1:FstJa9V+Pj9vQ7OJie2qMHdwemEDaDiSdBnvPM1Su9w=
|
||||
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
|
||||
github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
|
||||
github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio=
|
||||
github.com/vmihailenco/msgpack/v5 v5.1.3 h1:FwC9KPjyW8OqTUqMt6rQw9y50vA2cTLXPKCcBCRbQgg=
|
||||
github.com/vmihailenco/msgpack/v5 v5.1.3/go.mod h1:C5gboKD0TJPqWDTVTtrQNfRbiBwHZGo8UTqP/9/XvLI=
|
||||
@ -285,75 +437,165 @@ github.com/vmihailenco/tagparser v0.1.2/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgq
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
|
||||
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
||||
github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI=
|
||||
github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg=
|
||||
github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM=
|
||||
github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc=
|
||||
go.etcd.io/bbolt v1.3.5 h1:XAzx9gjCb0Rxj7EoqcClPD1d5ZBxZJk0jbuoPHenBt0=
|
||||
go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
|
||||
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU=
|
||||
go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4=
|
||||
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/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
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-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190418165655-df01cb2cc480/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
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/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=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
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/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/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/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
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-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190327091125-710a502c58a2/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190420063019-afa5a82059c6/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 h1:4nGaVu0QrbjT/AK2PRLuQfQuh6DJve+pELhqTdAj3x0=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20211008194852-3b03d305991f h1:1scJEYZBaF48BaG6tYbtxmLcXqwYGSfGcMoStTqkkIw=
|
||||
golang.org/x/net v0.0.0-20211008194852-3b03d305991f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
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=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
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-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/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=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/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-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190419153524-e8e3143a4f4a/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=
|
||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191025021431-6c3a3bfe00ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04 h1:cEhElsAv9LUt9ZUUocxzWe05oFLVd+AA2nstydTeI8g=
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44 h1:Bli41pIlzTzf3KEY06n+xnzK/BESIg2ze4Pgfh/aI8c=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
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-20210927094055-39ccf1dd6fa6/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-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220111092808-5a964db01320 h1:0jf+tOCoZ3LyutmCOWpVni1chK4VfFLhRsDK7MhqGRY=
|
||||
golang.org/x/sys v0.0.0-20220111092808-5a964db01320/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
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=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.5-0.20201125200606-c27b9fd57aec h1:A1qYjneJuzBZZ2gIB8rd6zrfq6l7SoEMJ8EsSilNK/U=
|
||||
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 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
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/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20181221001348-537d06c36207/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190327201419-c70d86f8b7cf/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190420181800-aa740d480789/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
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 h1:yBHHx+XZqXJBm6Exke3N7V9gnlsyXxoCPEb1yVenjfk=
|
||||
golang.org/x/tools v0.0.0-20200117012304-6edc0a871e69/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
@ -361,18 +603,57 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
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/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=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
|
||||
gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y=
|
||||
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
|
||||
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg=
|
||||
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
|
||||
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.v3 v3.0.0-20191120175047-4206685974f2/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
howett.net/plist v0.0.0-20181124034731-591f970eefbb/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0=
|
||||
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=
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2021 Proton Technologies AG
|
||||
// Copyright (c) 2022 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2021 Proton Technologies AG
|
||||
// Copyright (c) 2022 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2021 Proton Technologies AG
|
||||
// Copyright (c) 2022 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2021 Proton Technologies AG
|
||||
// Copyright (c) 2022 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2021 Proton Technologies AG
|
||||
// Copyright (c) 2022 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
@ -73,7 +73,8 @@ const (
|
||||
FlagCLI = "cli"
|
||||
flagCLIShort = "c"
|
||||
flagRestart = "restart"
|
||||
flagLauncher = "launcher"
|
||||
FlagLauncher = "launcher"
|
||||
FlagNoWindow = "no-window"
|
||||
)
|
||||
|
||||
type Base struct {
|
||||
@ -188,6 +189,8 @@ func New( // nolint[funlen]
|
||||
|
||||
cm := pmapi.New(cfg)
|
||||
|
||||
sentryReporter.SetClientFromManager(cm)
|
||||
|
||||
cm.AddConnectionObserver(pmapi.NewConnectionObserver(
|
||||
func() { listener.Emit(events.InternetOffEvent, "") },
|
||||
func() { listener.Emit(events.InternetOnEvent, "") },
|
||||
@ -235,7 +238,7 @@ func New( // nolint[funlen]
|
||||
autostart := &autostart.App{
|
||||
Name: appName,
|
||||
DisplayName: appName,
|
||||
Exec: []string{exe},
|
||||
Exec: []string{exe, "--" + FlagNoWindow},
|
||||
}
|
||||
|
||||
return &Base{
|
||||
@ -264,13 +267,13 @@ func New( // nolint[funlen]
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (b *Base) NewApp(action func(*Base, *cli.Context) error) *cli.App {
|
||||
func (b *Base) NewApp(mainLoop func(*Base, *cli.Context) error) *cli.App {
|
||||
app := cli.NewApp()
|
||||
|
||||
app.Name = b.Name
|
||||
app.Usage = b.usage
|
||||
app.Version = constants.Version
|
||||
app.Action = b.run(action)
|
||||
app.Action = b.wrapMainLoop(mainLoop)
|
||||
app.Flags = []cli.Flag{
|
||||
&cli.BoolFlag{
|
||||
Name: flagCPUProfile,
|
||||
@ -292,13 +295,17 @@ func (b *Base) NewApp(action func(*Base, *cli.Context) error) *cli.App {
|
||||
Aliases: []string{flagCLIShort},
|
||||
Usage: "Use command line interface",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: FlagNoWindow,
|
||||
Usage: "Don't show window after start",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: flagRestart,
|
||||
Usage: "The number of times the application has already restarted",
|
||||
Hidden: true,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: flagLauncher,
|
||||
Name: FlagLauncher,
|
||||
Usage: "The launcher to use to restart the application",
|
||||
Hidden: true,
|
||||
},
|
||||
@ -317,15 +324,18 @@ func (b *Base) AddTeardownAction(fn func() error) {
|
||||
b.teardown = append(b.teardown, fn)
|
||||
}
|
||||
|
||||
func (b *Base) run(appMainLoop func(*Base, *cli.Context) error) cli.ActionFunc { // nolint[funlen]
|
||||
func (b *Base) wrapMainLoop(appMainLoop func(*Base, *cli.Context) error) cli.ActionFunc { // nolint[funlen]
|
||||
return func(c *cli.Context) error {
|
||||
defer b.CrashHandler.HandlePanic()
|
||||
defer func() { _ = b.Lock.Close() }()
|
||||
|
||||
// If launcher was used to start the app, use that for restart/autostart.
|
||||
if launcher := c.String(flagLauncher); launcher != "" {
|
||||
b.Autostart.Exec = []string{launcher}
|
||||
// If launcher was used to start the app, use that for restart
|
||||
// and autostart.
|
||||
if launcher := c.String(FlagLauncher); launcher != "" {
|
||||
b.command = launcher
|
||||
// Bridge supports no-window option which we should use
|
||||
// for autostart.
|
||||
b.Autostart.Exec = []string{launcher, "--" + FlagNoWindow}
|
||||
}
|
||||
|
||||
if c.Bool(flagCPUProfile) {
|
||||
@ -350,10 +360,13 @@ func (b *Base) run(appMainLoop func(*Base, *cli.Context) error) cli.ActionFunc {
|
||||
Info("Run app")
|
||||
|
||||
b.CrashHandler.AddRecoveryAction(func(interface{}) error {
|
||||
sentry.Flush(2 * time.Second)
|
||||
|
||||
if c.Int(flagRestart) > maxAllowedRestarts {
|
||||
logrus.
|
||||
WithField("restart", c.Int("restart")).
|
||||
Warn("Not restarting, already restarted too many times")
|
||||
os.Exit(1)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2021 Proton Technologies AG
|
||||
// Copyright (c) 2022 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2021 Proton Technologies AG
|
||||
// Copyright (c) 2022 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2021 Proton Technologies AG
|
||||
// Copyright (c) 2022 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
@ -33,6 +33,7 @@ func (b *Base) restartApp(crash bool) error {
|
||||
|
||||
if crash {
|
||||
args = incrementRestartFlag(os.Args)[1:]
|
||||
defer func() { os.Exit(1) }()
|
||||
} else {
|
||||
args = os.Args[1:]
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2021 Proton Technologies AG
|
||||
// Copyright (c) 2022 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2021 Proton Technologies AG
|
||||
// Copyright (c) 2022 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
@ -24,7 +24,7 @@ import (
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/internal/api"
|
||||
"github.com/ProtonMail/proton-bridge/internal/app/base"
|
||||
"github.com/ProtonMail/proton-bridge/internal/bridge"
|
||||
pkgBridge "github.com/ProtonMail/proton-bridge/internal/bridge"
|
||||
"github.com/ProtonMail/proton-bridge/internal/config/settings"
|
||||
pkgTLS "github.com/ProtonMail/proton-bridge/internal/config/tls"
|
||||
"github.com/ProtonMail/proton-bridge/internal/constants"
|
||||
@ -32,7 +32,10 @@ import (
|
||||
"github.com/ProtonMail/proton-bridge/internal/frontend/types"
|
||||
"github.com/ProtonMail/proton-bridge/internal/imap"
|
||||
"github.com/ProtonMail/proton-bridge/internal/smtp"
|
||||
"github.com/ProtonMail/proton-bridge/internal/store"
|
||||
"github.com/ProtonMail/proton-bridge/internal/store/cache"
|
||||
"github.com/ProtonMail/proton-bridge/internal/updater"
|
||||
"github.com/ProtonMail/proton-bridge/pkg/message"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli/v2"
|
||||
@ -41,12 +44,15 @@ import (
|
||||
const (
|
||||
flagLogIMAP = "log-imap"
|
||||
flagLogSMTP = "log-smtp"
|
||||
flagNoWindow = "no-window"
|
||||
flagNonInteractive = "noninteractive"
|
||||
|
||||
// Memory cache was estimated by empirical usage in past and it was set to 100MB.
|
||||
// NOTE: This value must not be less than maximal size of one email (~30MB).
|
||||
inMemoryCacheLimnit = 100 * (1 << 20)
|
||||
)
|
||||
|
||||
func New(base *base.Base) *cli.App {
|
||||
app := base.NewApp(run)
|
||||
app := base.NewApp(mailLoop)
|
||||
|
||||
app.Flags = append(app.Flags, []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
@ -55,9 +61,6 @@ func New(base *base.Base) *cli.App {
|
||||
&cli.BoolFlag{
|
||||
Name: flagLogSMTP,
|
||||
Usage: "Enable logging of SMTP communications (may contain decrypted data!)"},
|
||||
&cli.BoolFlag{
|
||||
Name: flagNoWindow,
|
||||
Usage: "Don't show window after start"},
|
||||
&cli.BoolFlag{
|
||||
Name: flagNonInteractive,
|
||||
Usage: "Start Bridge entirely noninteractively"},
|
||||
@ -66,15 +69,47 @@ func New(base *base.Base) *cli.App {
|
||||
return app
|
||||
}
|
||||
|
||||
func run(b *base.Base, c *cli.Context) error { // nolint[funlen]
|
||||
func mailLoop(b *base.Base, c *cli.Context) error { // nolint[funlen]
|
||||
tlsConfig, err := loadTLSConfig(b)
|
||||
if err != nil {
|
||||
logrus.WithError(err).Fatal("Failed to load TLS config")
|
||||
return err
|
||||
}
|
||||
bridge := bridge.New(b.Locations, b.Cache, b.Settings, b.SentryReporter, b.CrashHandler, b.Listener, b.CM, b.Creds, b.Updater, b.Versioner)
|
||||
imapBackend := imap.NewIMAPBackend(b.CrashHandler, b.Listener, b.Cache, bridge)
|
||||
|
||||
// GODT-1481: Always turn off reporting of unencrypted recipient in v2.
|
||||
b.Settings.SetBool(settings.ReportOutgoingNoEncKey, false)
|
||||
|
||||
cache, cacheErr := loadMessageCache(b)
|
||||
if cacheErr != nil {
|
||||
logrus.WithError(cacheErr).Error("Could not load local cache.")
|
||||
}
|
||||
|
||||
builder := message.NewBuilder(
|
||||
b.Settings.GetInt(settings.FetchWorkers),
|
||||
b.Settings.GetInt(settings.AttachmentWorkers),
|
||||
)
|
||||
|
||||
bridge := pkgBridge.New(
|
||||
b.Locations,
|
||||
b.Cache,
|
||||
b.Settings,
|
||||
b.SentryReporter,
|
||||
b.CrashHandler,
|
||||
b.Listener,
|
||||
cache,
|
||||
builder,
|
||||
b.CM,
|
||||
b.Creds,
|
||||
b.Updater,
|
||||
b.Versioner,
|
||||
b.Autostart,
|
||||
)
|
||||
imapBackend := imap.NewIMAPBackend(b.CrashHandler, b.Listener, b.Cache, b.Settings, bridge)
|
||||
smtpBackend := smtp.NewSMTPBackend(b.CrashHandler, b.Listener, b.Settings, bridge)
|
||||
|
||||
if cacheErr != nil {
|
||||
bridge.AddError(pkgBridge.ErrLocalCacheUnavailable)
|
||||
}
|
||||
|
||||
go func() {
|
||||
defer b.CrashHandler.HandlePanic()
|
||||
api.NewAPIServer(b.Settings, b.Listener).ListenAndServe()
|
||||
@ -100,9 +135,6 @@ func run(b *base.Base, c *cli.Context) error { // nolint[funlen]
|
||||
smtpPort, useSSL, tlsConfig, smtpBackend, b.Listener).ListenAndServe()
|
||||
}()
|
||||
|
||||
// Bridge supports no-window option which we should use for autostart.
|
||||
b.Autostart.Exec = append(b.Autostart.Exec, "--"+flagNoWindow)
|
||||
|
||||
// We want to remove old versions if the app exits successfully.
|
||||
b.AddTeardownAction(b.Versioner.RemoveOldVersions)
|
||||
|
||||
@ -125,7 +157,7 @@ func run(b *base.Base, c *cli.Context) error { // nolint[funlen]
|
||||
constants.BuildVersion,
|
||||
b.Name,
|
||||
frontendMode,
|
||||
!c.Bool(flagNoWindow),
|
||||
!c.Bool(base.FlagNoWindow),
|
||||
b.CrashHandler,
|
||||
b.Locations,
|
||||
b.Settings,
|
||||
@ -134,7 +166,6 @@ func run(b *base.Base, c *cli.Context) error { // nolint[funlen]
|
||||
b.UserAgent,
|
||||
bridge,
|
||||
smtpBackend,
|
||||
b.Autostart,
|
||||
b,
|
||||
)
|
||||
|
||||
@ -233,3 +264,51 @@ func checkAndHandleUpdate(u types.Updater, f frontend.Frontend, autoUpdate bool)
|
||||
|
||||
f.NotifySilentUpdateInstalled()
|
||||
}
|
||||
|
||||
// loadMessageCache loads local cache in case it is enabled in settings and available.
|
||||
// In any other case it is returning in-memory cache. Could also return an error in case
|
||||
// local cache is enabled but unavailable (in-memory cache will be returned nevertheless).
|
||||
func loadMessageCache(b *base.Base) (cache.Cache, error) {
|
||||
if !b.Settings.GetBool(settings.CacheEnabledKey) {
|
||||
return cache.NewInMemoryCache(inMemoryCacheLimnit), nil
|
||||
}
|
||||
|
||||
var compressor cache.Compressor
|
||||
|
||||
// NOTE(GODT-1158): Changing compression is not an option currently
|
||||
// available for user but, if user changes compression setting we have
|
||||
// to nuke the cache.
|
||||
if b.Settings.GetBool(settings.CacheCompressionKey) {
|
||||
compressor = &cache.GZipCompressor{}
|
||||
} else {
|
||||
compressor = &cache.NoopCompressor{}
|
||||
}
|
||||
|
||||
var path string
|
||||
|
||||
if customPath := b.Settings.Get(settings.CacheLocationKey); customPath != "" {
|
||||
path = customPath
|
||||
} else {
|
||||
path = b.Cache.GetDefaultMessageCacheDir()
|
||||
// Store path so it will allways persist if default location
|
||||
// will be changed in new version.
|
||||
b.Settings.Set(settings.CacheLocationKey, path)
|
||||
}
|
||||
|
||||
// To prevent memory peaks we set maximal write concurency for store
|
||||
// build jobs.
|
||||
store.SetBuildAndCacheJobLimit(b.Settings.GetInt(settings.CacheConcurrencyWrite))
|
||||
|
||||
messageCache, err := cache.NewOnDiskCache(path, compressor, cache.Options{
|
||||
MinFreeAbs: uint64(b.Settings.GetInt(settings.CacheMinFreeAbsKey)),
|
||||
MinFreeRat: b.Settings.GetFloat64(settings.CacheMinFreeRatKey),
|
||||
ConcurrentRead: b.Settings.GetInt(settings.CacheConcurrencyRead),
|
||||
ConcurrentWrite: b.Settings.GetInt(settings.CacheConcurrencyWrite),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return cache.NewInMemoryCache(inMemoryCacheLimnit), err
|
||||
}
|
||||
|
||||
return messageCache, nil
|
||||
}
|
||||
|
||||
@ -1,110 +0,0 @@
|
||||
// Copyright (c) 2021 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
// ProtonMail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// ProtonMail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
// Package ie implements the ie CLI application.
|
||||
package ie
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/internal/api"
|
||||
"github.com/ProtonMail/proton-bridge/internal/app/base"
|
||||
"github.com/ProtonMail/proton-bridge/internal/config/settings"
|
||||
"github.com/ProtonMail/proton-bridge/internal/constants"
|
||||
"github.com/ProtonMail/proton-bridge/internal/frontend"
|
||||
"github.com/ProtonMail/proton-bridge/internal/frontend/types"
|
||||
"github.com/ProtonMail/proton-bridge/internal/importexport"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func New(b *base.Base) *cli.App {
|
||||
return b.NewApp(run)
|
||||
}
|
||||
|
||||
func run(b *base.Base, c *cli.Context) error {
|
||||
ie := importexport.New(b.Locations, b.Cache, b.CrashHandler, b.Listener, b.CM, b.Creds)
|
||||
|
||||
go func() {
|
||||
defer b.CrashHandler.HandlePanic()
|
||||
api.NewAPIServer(b.Settings, b.Listener).ListenAndServe()
|
||||
}()
|
||||
|
||||
var frontendMode string
|
||||
|
||||
switch {
|
||||
case c.Bool(base.FlagCLI):
|
||||
frontendMode = "cli"
|
||||
default:
|
||||
frontendMode = "qt"
|
||||
}
|
||||
|
||||
// We want to remove old versions if the app exits successfully.
|
||||
b.AddTeardownAction(b.Versioner.RemoveOldVersions)
|
||||
|
||||
// We want cookies to be saved to disk so they are loaded the next time.
|
||||
b.AddTeardownAction(b.CookieJar.PersistCookies)
|
||||
|
||||
f := frontend.NewImportExport(
|
||||
constants.Version,
|
||||
constants.BuildVersion,
|
||||
b.Name,
|
||||
frontendMode,
|
||||
b.CrashHandler,
|
||||
b.Locations,
|
||||
b.Settings,
|
||||
b.Listener,
|
||||
b.Updater,
|
||||
ie,
|
||||
b,
|
||||
)
|
||||
|
||||
// Watch for updates routine
|
||||
go func() {
|
||||
ticker := time.NewTicker(time.Hour)
|
||||
|
||||
for {
|
||||
checkAndHandleUpdate(b.Updater, f, b.Settings.GetBool(settings.AutoUpdateKey))
|
||||
<-ticker.C
|
||||
}
|
||||
}()
|
||||
|
||||
return f.Loop()
|
||||
}
|
||||
|
||||
func checkAndHandleUpdate(u types.Updater, f frontend.Frontend, autoUpdate bool) { //nolint[unparam]
|
||||
log := logrus.WithField("pkg", "app/ie")
|
||||
version, err := u.Check()
|
||||
if err != nil {
|
||||
log.WithError(err).Error("An error occurred while checking for updates")
|
||||
return
|
||||
}
|
||||
|
||||
f.WaitUntilFrontendIsReady()
|
||||
|
||||
// Update links in UI
|
||||
f.SetVersion(version)
|
||||
|
||||
if !u.IsUpdateApplicable(version) {
|
||||
log.Info("No need to update")
|
||||
return
|
||||
}
|
||||
|
||||
log.WithField("version", version.Version).Info("An update is available")
|
||||
|
||||
f.NotifyManualUpdate(version, u.CanInstall(version))
|
||||
}
|
||||
31
internal/bridge/autostart.go
Normal file
31
internal/bridge/autostart.go
Normal file
@ -0,0 +1,31 @@
|
||||
// Copyright (c) 2022 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
// ProtonMail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// ProtonMail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
// Package bridge provides core functionality of Bridge app.
|
||||
package bridge
|
||||
|
||||
func (b *Bridge) IsAutostartEnabled() bool {
|
||||
return b.autostart.IsEnabled()
|
||||
}
|
||||
|
||||
func (b *Bridge) EnableAutostart() error {
|
||||
return b.autostart.Enable()
|
||||
}
|
||||
|
||||
func (b *Bridge) DisableAutostart() error {
|
||||
return b.autostart.Disable()
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2021 Proton Technologies AG
|
||||
// Copyright (c) 2022 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
@ -19,26 +19,30 @@
|
||||
package bridge
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/Masterminds/semver/v3"
|
||||
"github.com/ProtonMail/go-autostart"
|
||||
"github.com/ProtonMail/proton-bridge/internal/config/settings"
|
||||
"github.com/ProtonMail/proton-bridge/internal/constants"
|
||||
"github.com/ProtonMail/proton-bridge/internal/metrics"
|
||||
"github.com/ProtonMail/proton-bridge/internal/sentry"
|
||||
"github.com/ProtonMail/proton-bridge/internal/store/cache"
|
||||
"github.com/ProtonMail/proton-bridge/internal/updater"
|
||||
"github.com/ProtonMail/proton-bridge/internal/users"
|
||||
"github.com/ProtonMail/proton-bridge/pkg/message"
|
||||
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/pkg/listener"
|
||||
logrus "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var (
|
||||
log = logrus.WithField("pkg", "bridge") //nolint[gochecknoglobals]
|
||||
)
|
||||
var log = logrus.WithField("pkg", "bridge") //nolint[gochecknoglobals]
|
||||
|
||||
var ErrLocalCacheUnavailable = errors.New("local cache is unavailable")
|
||||
|
||||
type Bridge struct {
|
||||
*users.Users
|
||||
@ -48,45 +52,72 @@ type Bridge struct {
|
||||
clientManager pmapi.Manager
|
||||
updater Updater
|
||||
versioner Versioner
|
||||
cacheProvider CacheProvider
|
||||
autostart *autostart.App
|
||||
// Bridge's global errors list.
|
||||
errors []error
|
||||
|
||||
isFirstStart bool
|
||||
lastVersion string
|
||||
}
|
||||
|
||||
func New(
|
||||
locations Locator,
|
||||
cache Cacher,
|
||||
s SettingsProvider,
|
||||
cacheProvider CacheProvider,
|
||||
setting SettingsProvider,
|
||||
sentryReporter *sentry.Reporter,
|
||||
panicHandler users.PanicHandler,
|
||||
eventListener listener.Listener,
|
||||
cache cache.Cache,
|
||||
builder *message.Builder,
|
||||
clientManager pmapi.Manager,
|
||||
credStorer users.CredentialsStorer,
|
||||
updater Updater,
|
||||
versioner Versioner,
|
||||
autostart *autostart.App,
|
||||
) *Bridge {
|
||||
// Allow DoH before starting the app if the user has previously set this setting.
|
||||
// This allows us to start even if protonmail is blocked.
|
||||
if s.GetBool(settings.AllowProxyKey) {
|
||||
if setting.GetBool(settings.AllowProxyKey) {
|
||||
clientManager.AllowProxy()
|
||||
}
|
||||
|
||||
storeFactory := newStoreFactory(cache, sentryReporter, panicHandler, eventListener)
|
||||
u := users.New(locations, panicHandler, eventListener, clientManager, credStorer, storeFactory, true)
|
||||
u := users.New(
|
||||
locations,
|
||||
panicHandler,
|
||||
eventListener,
|
||||
clientManager,
|
||||
credStorer,
|
||||
newStoreFactory(cacheProvider, sentryReporter, panicHandler, eventListener, cache, builder),
|
||||
)
|
||||
|
||||
b := &Bridge{
|
||||
Users: u,
|
||||
|
||||
locations: locations,
|
||||
settings: s,
|
||||
settings: setting,
|
||||
clientManager: clientManager,
|
||||
updater: updater,
|
||||
versioner: versioner,
|
||||
cacheProvider: cacheProvider,
|
||||
autostart: autostart,
|
||||
isFirstStart: false,
|
||||
}
|
||||
|
||||
if s.GetBool(settings.FirstStartKey) {
|
||||
if setting.GetBool(settings.FirstStartKey) {
|
||||
b.isFirstStart = true
|
||||
if err := b.SendMetric(metrics.New(metrics.Setup, metrics.FirstStart, metrics.Label(constants.Version))); err != nil {
|
||||
logrus.WithError(err).Error("Failed to send metric")
|
||||
}
|
||||
|
||||
s.SetBool(settings.FirstStartKey, false)
|
||||
if err := b.EnableAutostart(); err != nil {
|
||||
log.WithError(err).Error("Failed to enable autostart")
|
||||
}
|
||||
setting.SetBool(settings.FirstStartKey, false)
|
||||
}
|
||||
|
||||
// Keep in bridge and update in settings the last used version.
|
||||
b.lastVersion = b.settings.Get(settings.LastVersionKey)
|
||||
b.settings.Set(settings.LastVersionKey, constants.Version)
|
||||
|
||||
go b.heartbeat()
|
||||
|
||||
@ -117,55 +148,60 @@ func (b *Bridge) heartbeat() {
|
||||
}
|
||||
}
|
||||
|
||||
// ReportBug reports a new bug from the user.
|
||||
func (b *Bridge) ReportBug(osType, osVersion, description, accountName, address, emailClient string) error {
|
||||
return b.clientManager.ReportBug(context.Background(), pmapi.ReportBugReq{
|
||||
OS: osType,
|
||||
OSVersion: osVersion,
|
||||
Browser: emailClient,
|
||||
Title: "[Bridge] Bug",
|
||||
Description: description,
|
||||
Username: accountName,
|
||||
Email: address,
|
||||
})
|
||||
}
|
||||
|
||||
// GetUpdateChannel returns currently set update channel.
|
||||
func (b *Bridge) GetUpdateChannel() updater.UpdateChannel {
|
||||
return updater.UpdateChannel(b.settings.Get(settings.UpdateChannelKey))
|
||||
}
|
||||
|
||||
// SetUpdateChannel switches update channel.
|
||||
// Downgrading to previous version (by switching from early to stable, for example)
|
||||
// requires clearing all data including update files due to possibility of
|
||||
// inconsistency between versions and absence of backwards migration scripts.
|
||||
func (b *Bridge) SetUpdateChannel(channel updater.UpdateChannel) (needRestart bool, err error) {
|
||||
func (b *Bridge) SetUpdateChannel(channel updater.UpdateChannel) {
|
||||
b.settings.Set(settings.UpdateChannelKey, string(channel))
|
||||
}
|
||||
|
||||
func (b *Bridge) resetToLatestStable() error {
|
||||
version, err := b.updater.Check()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// We have to deal right away only with downgrade - that action needs to
|
||||
// clear data and updates, and install bridge right away. But regular
|
||||
// upgrade can be leaved out for periodic check.
|
||||
if !b.updater.IsDowngrade(version) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if err := b.Users.ClearData(); err != nil {
|
||||
log.WithError(err).Error("Failed to clear data while downgrading channel")
|
||||
}
|
||||
if err := b.locations.ClearUpdates(); err != nil {
|
||||
// If we can not check for updates - just remove all local updates and reset to base installer version.
|
||||
// Not using `b.locations.ClearUpdates()` because `versioner.RemoveOtherVersions` can also handle
|
||||
// case when it is needed to remove currently running verion.
|
||||
if err := b.versioner.RemoveOtherVersions(semver.MustParse("0.0.0")); err != nil {
|
||||
log.WithError(err).Error("Failed to clear updates while downgrading channel")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// If current version is same as upstream stable version - do nothing.
|
||||
if version.Version.Equal(semver.MustParse(constants.Version)) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := b.updater.InstallUpdate(version); err != nil {
|
||||
return false, err
|
||||
return err
|
||||
}
|
||||
|
||||
return true, b.versioner.RemoveOtherVersions(version.Version)
|
||||
return b.versioner.RemoveOtherVersions(version.Version)
|
||||
}
|
||||
|
||||
// FactoryReset will remove all local cache and settings.
|
||||
// It will also downgrade to latest stable version if user is on early version.
|
||||
func (b *Bridge) FactoryReset() {
|
||||
wasEarly := b.GetUpdateChannel() == updater.EarlyChannel
|
||||
|
||||
b.settings.Set(settings.UpdateChannelKey, string(updater.StableChannel))
|
||||
|
||||
if wasEarly {
|
||||
if err := b.resetToLatestStable(); err != nil {
|
||||
log.WithError(err).Error("Failed to reset to latest stable version")
|
||||
}
|
||||
}
|
||||
|
||||
if err := b.Users.ClearData(); err != nil {
|
||||
log.WithError(err).Error("Failed to remove bridge data")
|
||||
}
|
||||
|
||||
if err := b.Users.ClearUsers(); err != nil {
|
||||
log.WithError(err).Error("Failed to remove bridge users")
|
||||
}
|
||||
}
|
||||
|
||||
// GetKeychainApp returns current keychain helper.
|
||||
@ -177,3 +213,96 @@ func (b *Bridge) GetKeychainApp() string {
|
||||
func (b *Bridge) SetKeychainApp(helper string) {
|
||||
b.settings.Set(settings.PreferredKeychainKey, helper)
|
||||
}
|
||||
|
||||
func (b *Bridge) EnableCache() error {
|
||||
if err := b.Users.EnableCache(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b.settings.SetBool(settings.CacheEnabledKey, true)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Bridge) DisableCache() error {
|
||||
if err := b.Users.DisableCache(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b.settings.SetBool(settings.CacheEnabledKey, false)
|
||||
// Reset back to the default location when disabling.
|
||||
b.settings.Set(settings.CacheLocationKey, b.cacheProvider.GetDefaultMessageCacheDir())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Bridge) MigrateCache(from, to string) error {
|
||||
if err := b.Users.MigrateCache(from, to); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b.settings.Set(settings.CacheLocationKey, to)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetProxyAllowed instructs the app whether to use DoH to access an API proxy if necessary.
|
||||
// It also needs to work before the app is initialised (because we may need to use the proxy at startup).
|
||||
func (b *Bridge) SetProxyAllowed(proxyAllowed bool) {
|
||||
b.settings.SetBool(settings.AllowProxyKey, proxyAllowed)
|
||||
if proxyAllowed {
|
||||
b.clientManager.AllowProxy()
|
||||
} else {
|
||||
b.clientManager.DisallowProxy()
|
||||
}
|
||||
}
|
||||
|
||||
// GetProxyAllowed returns whether use of DoH is enabled to access an API proxy if necessary.
|
||||
func (b *Bridge) GetProxyAllowed() bool {
|
||||
return b.settings.GetBool(settings.AllowProxyKey)
|
||||
}
|
||||
|
||||
// AddError add an error to a global error list if it does not contain it yet. Adding nil is noop.
|
||||
func (b *Bridge) AddError(err error) {
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
if b.HasError(err) {
|
||||
return
|
||||
}
|
||||
|
||||
b.errors = append(b.errors, err)
|
||||
}
|
||||
|
||||
// DelError removes an error from global error list.
|
||||
func (b *Bridge) DelError(err error) {
|
||||
for idx, val := range b.errors {
|
||||
if val == err {
|
||||
b.errors = append(b.errors[:idx], b.errors[idx+1:]...)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// HasError returnes true if global error list contains an err.
|
||||
func (b *Bridge) HasError(err error) bool {
|
||||
for _, val := range b.errors {
|
||||
if val == err {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// GetLastVersion returns the version which was used in previous execution of
|
||||
// Bridge.
|
||||
func (b *Bridge) GetLastVersion() string {
|
||||
return b.lastVersion
|
||||
}
|
||||
|
||||
// IsFirstStart returns true when Bridge is running for first time or after
|
||||
// factory reset.
|
||||
func (b *Bridge) IsFirstStart() bool {
|
||||
return b.isFirstStart
|
||||
}
|
||||
|
||||
199
internal/bridge/bug_report.go
Normal file
199
internal/bridge/bug_report.go
Normal file
@ -0,0 +1,199 @@
|
||||
// Copyright (c) 2022 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
// ProtonMail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// ProtonMail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package bridge
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/internal/logging"
|
||||
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
||||
)
|
||||
|
||||
const MaxAttachmentSize = 7 * 1024 * 1024 // 7 MB total limit
|
||||
const MaxCompressedFilesCount = 6
|
||||
|
||||
var ErrSizeTooLarge = errors.New("file is too big")
|
||||
|
||||
// ReportBug reports a new bug from the user.
|
||||
func (b *Bridge) ReportBug(osType, osVersion, description, accountName, address, emailClient string, attachLogs bool) error {
|
||||
report := pmapi.ReportBugReq{
|
||||
OS: osType,
|
||||
OSVersion: osVersion,
|
||||
Browser: emailClient,
|
||||
Title: "[Bridge] Bug",
|
||||
Description: description,
|
||||
Username: accountName,
|
||||
Email: address,
|
||||
}
|
||||
|
||||
if attachLogs {
|
||||
logs, err := b.getMatchingLogs(
|
||||
func(filename string) bool {
|
||||
return logging.MatchLogName(filename) && !logging.MatchStackTraceName(filename)
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Can't get log files list")
|
||||
}
|
||||
crashes, err := b.getMatchingLogs(
|
||||
func(filename string) bool {
|
||||
return logging.MatchLogName(filename) && logging.MatchStackTraceName(filename)
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Can't get crash files list")
|
||||
}
|
||||
|
||||
var matchFiles []string
|
||||
|
||||
matchFiles = append(matchFiles, logs[max(0, len(logs)-(MaxCompressedFilesCount/2)):]...)
|
||||
matchFiles = append(matchFiles, crashes[max(0, len(crashes)-(MaxCompressedFilesCount/2)):]...)
|
||||
|
||||
archive, err := zipFiles(matchFiles)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Can't zip logs and crashes")
|
||||
}
|
||||
|
||||
if archive != nil {
|
||||
report.AddAttachment("logs.zip", "application/zip", archive)
|
||||
}
|
||||
}
|
||||
|
||||
return b.clientManager.ReportBug(context.Background(), report)
|
||||
}
|
||||
|
||||
func max(a, b int) int {
|
||||
if a > b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *Bridge) getMatchingLogs(filenameMatchFunc func(string) bool) (filenames []string, err error) {
|
||||
logsPath, err := b.locations.ProvideLogsPath()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
files, err := ioutil.ReadDir(logsPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var matchFiles []string
|
||||
|
||||
for _, file := range files {
|
||||
if filenameMatchFunc(file.Name()) {
|
||||
matchFiles = append(matchFiles, filepath.Join(logsPath, file.Name()))
|
||||
}
|
||||
}
|
||||
sort.Strings(matchFiles) // Sorted by timestamp: oldest first.
|
||||
|
||||
return matchFiles, nil
|
||||
}
|
||||
|
||||
type LimitedBuffer struct {
|
||||
capacity int
|
||||
buf *bytes.Buffer
|
||||
}
|
||||
|
||||
func NewLimitedBuffer(capacity int) *LimitedBuffer {
|
||||
return &LimitedBuffer{
|
||||
capacity: capacity,
|
||||
buf: bytes.NewBuffer(make([]byte, 0, capacity)),
|
||||
}
|
||||
}
|
||||
|
||||
func (b *LimitedBuffer) Write(p []byte) (n int, err error) {
|
||||
if len(p)+b.buf.Len() > b.capacity {
|
||||
return 0, ErrSizeTooLarge
|
||||
}
|
||||
|
||||
return b.buf.Write(p)
|
||||
}
|
||||
|
||||
func (b *LimitedBuffer) Read(p []byte) (n int, err error) {
|
||||
return b.buf.Read(p)
|
||||
}
|
||||
|
||||
func zipFiles(filenames []string) (io.Reader, error) {
|
||||
if len(filenames) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
buf := NewLimitedBuffer(MaxAttachmentSize)
|
||||
|
||||
w := zip.NewWriter(buf)
|
||||
defer w.Close() //nolint[errcheck]
|
||||
|
||||
for _, file := range filenames {
|
||||
err := addFileToZip(file, w)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if err := w.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
func addFileToZip(filename string, writer *zip.Writer) error {
|
||||
fileReader, err := os.Open(filepath.Clean(filename))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer fileReader.Close() //nolint[errcheck]
|
||||
|
||||
fileInfo, err := fileReader.Stat()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
header, err := zip.FileInfoHeader(fileInfo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
header.Method = zip.Deflate
|
||||
header.Name = filepath.Base(filename)
|
||||
|
||||
fileWriter, err := writer.CreateHeader(header)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = io.Copy(fileWriter, fileReader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = fileReader.Close()
|
||||
|
||||
return err
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2021 Proton Technologies AG
|
||||
// Copyright (c) 2022 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2021 Proton Technologies AG
|
||||
// Copyright (c) 2022 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2021 Proton Technologies AG
|
||||
// Copyright (c) 2022 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
@ -23,47 +23,65 @@ import (
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/internal/sentry"
|
||||
"github.com/ProtonMail/proton-bridge/internal/store"
|
||||
"github.com/ProtonMail/proton-bridge/internal/store/cache"
|
||||
"github.com/ProtonMail/proton-bridge/internal/users"
|
||||
"github.com/ProtonMail/proton-bridge/pkg/listener"
|
||||
"github.com/ProtonMail/proton-bridge/pkg/message"
|
||||
)
|
||||
|
||||
type storeFactory struct {
|
||||
cache Cacher
|
||||
cacheProvider CacheProvider
|
||||
sentryReporter *sentry.Reporter
|
||||
panicHandler users.PanicHandler
|
||||
eventListener listener.Listener
|
||||
storeCache *store.Cache
|
||||
events *store.Events
|
||||
cache cache.Cache
|
||||
builder *message.Builder
|
||||
}
|
||||
|
||||
func newStoreFactory(
|
||||
cache Cacher,
|
||||
cacheProvider CacheProvider,
|
||||
sentryReporter *sentry.Reporter,
|
||||
panicHandler users.PanicHandler,
|
||||
eventListener listener.Listener,
|
||||
cache cache.Cache,
|
||||
builder *message.Builder,
|
||||
) *storeFactory {
|
||||
return &storeFactory{
|
||||
cache: cache,
|
||||
cacheProvider: cacheProvider,
|
||||
sentryReporter: sentryReporter,
|
||||
panicHandler: panicHandler,
|
||||
eventListener: eventListener,
|
||||
storeCache: store.NewCache(cache.GetIMAPCachePath()),
|
||||
events: store.NewEvents(cacheProvider.GetIMAPCachePath()),
|
||||
cache: cache,
|
||||
builder: builder,
|
||||
}
|
||||
}
|
||||
|
||||
// New creates new store for given user.
|
||||
func (f *storeFactory) New(user store.BridgeUser) (*store.Store, error) {
|
||||
storePath := getUserStorePath(f.cache.GetDBDir(), user.ID())
|
||||
return store.New(f.sentryReporter, f.panicHandler, user, f.eventListener, storePath, f.storeCache)
|
||||
return store.New(
|
||||
f.sentryReporter,
|
||||
f.panicHandler,
|
||||
user,
|
||||
f.eventListener,
|
||||
f.cache,
|
||||
f.builder,
|
||||
getUserStorePath(f.cacheProvider.GetDBDir(), user.ID()),
|
||||
f.events,
|
||||
)
|
||||
}
|
||||
|
||||
// Remove removes all store files for given user.
|
||||
func (f *storeFactory) Remove(userID string) error {
|
||||
storePath := getUserStorePath(f.cache.GetDBDir(), userID)
|
||||
return store.RemoveStore(f.storeCache, storePath, userID)
|
||||
return store.RemoveStore(
|
||||
f.events,
|
||||
getUserStorePath(f.cacheProvider.GetDBDir(), userID),
|
||||
userID,
|
||||
)
|
||||
}
|
||||
|
||||
// getUserStorePath returns the file path of the store database for the given userID.
|
||||
func getUserStorePath(storeDir string, userID string) (path string) {
|
||||
fileName := fmt.Sprintf("mailbox-%v.db", userID)
|
||||
return filepath.Join(storeDir, fileName)
|
||||
return filepath.Join(storeDir, fmt.Sprintf("mailbox-%v.db", userID))
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2021 Proton Technologies AG
|
||||
// Copyright (c) 2022 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
@ -26,11 +26,13 @@ import (
|
||||
type Locator interface {
|
||||
Clear() error
|
||||
ClearUpdates() error
|
||||
ProvideLogsPath() (string, error)
|
||||
}
|
||||
|
||||
type Cacher interface {
|
||||
type CacheProvider interface {
|
||||
GetIMAPCachePath() string
|
||||
GetDBDir() string
|
||||
GetDefaultMessageCacheDir() string
|
||||
}
|
||||
|
||||
type SettingsProvider interface {
|
||||
@ -38,6 +40,7 @@ type SettingsProvider interface {
|
||||
Set(key string, value string)
|
||||
GetBool(key string) bool
|
||||
SetBool(key string, val bool)
|
||||
GetInt(key string) int
|
||||
}
|
||||
|
||||
type Updater interface {
|
||||
|
||||
7
internal/config/cache/cache.go
vendored
7
internal/config/cache/cache.go
vendored
@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2021 Proton Technologies AG
|
||||
// Copyright (c) 2022 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
@ -45,6 +45,11 @@ func (c *Cache) GetDBDir() string {
|
||||
return c.getCurrentCacheDir()
|
||||
}
|
||||
|
||||
// GetDefaultMessageCacheDir returns folder for cached messages files.
|
||||
func (c *Cache) GetDefaultMessageCacheDir() string {
|
||||
return filepath.Join(c.getCurrentCacheDir(), "messages")
|
||||
}
|
||||
|
||||
// GetIMAPCachePath returns path to file with IMAP status.
|
||||
func (c *Cache) GetIMAPCachePath() string {
|
||||
return filepath.Join(c.getCurrentCacheDir(), "user_info.json")
|
||||
|
||||
2
internal/config/cache/cache_test.go
vendored
2
internal/config/cache/cache_test.go
vendored
@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2021 Proton Technologies AG
|
||||
// Copyright (c) 2022 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2021 Proton Technologies AG
|
||||
// Copyright (c) 2022 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
@ -100,18 +100,28 @@ func (p *keyValueStore) GetBool(key string) bool {
|
||||
}
|
||||
|
||||
func (p *keyValueStore) GetInt(key string) int {
|
||||
if p.Get(key) == "" {
|
||||
return 0
|
||||
}
|
||||
|
||||
value, err := strconv.Atoi(p.Get(key))
|
||||
if err != nil {
|
||||
logrus.WithError(err).Error("Cannot parse int")
|
||||
}
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
func (p *keyValueStore) GetFloat64(key string) float64 {
|
||||
if p.Get(key) == "" {
|
||||
return 0
|
||||
}
|
||||
|
||||
value, err := strconv.ParseFloat(p.Get(key), 64)
|
||||
if err != nil {
|
||||
logrus.WithError(err).Error("Cannot parse float64")
|
||||
}
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2021 Proton Technologies AG
|
||||
// Copyright (c) 2022 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
@ -25,81 +25,118 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
const testPrefFilePath = "/tmp/pref.json"
|
||||
|
||||
func TestLoadNoKeyValueStore(t *testing.T) {
|
||||
pref := newTestEmptyKeyValueStore(t)
|
||||
require.Equal(t, "", pref.Get("key"))
|
||||
r := require.New(t)
|
||||
pref, clean := newTestEmptyKeyValueStore(r)
|
||||
defer clean()
|
||||
|
||||
r.Equal("", pref.Get("key"))
|
||||
}
|
||||
|
||||
func TestLoadBadKeyValueStore(t *testing.T) {
|
||||
require.NoError(t, ioutil.WriteFile(testPrefFilePath, []byte("{\"key\":\"value"), 0700))
|
||||
pref := newKeyValueStore(testPrefFilePath)
|
||||
require.Equal(t, "", pref.Get("key"))
|
||||
r := require.New(t)
|
||||
path, clean := newTmpFile(r)
|
||||
defer clean()
|
||||
|
||||
r.NoError(ioutil.WriteFile(path, []byte("{\"key\":\"MISSING_QUOTES"), 0700))
|
||||
pref := newKeyValueStore(path)
|
||||
r.Equal("", pref.Get("key"))
|
||||
}
|
||||
|
||||
func TestKeyValueStoreGet(t *testing.T) {
|
||||
pref := newTestKeyValueStore(t)
|
||||
require.Equal(t, "value", pref.Get("str"))
|
||||
require.Equal(t, "42", pref.Get("int"))
|
||||
require.Equal(t, "true", pref.Get("bool"))
|
||||
require.Equal(t, "t", pref.Get("falseBool"))
|
||||
func TestKeyValueStor(t *testing.T) {
|
||||
r := require.New(t)
|
||||
pref, clean := newTestKeyValueStore(r)
|
||||
defer clean()
|
||||
|
||||
r.Equal("value", pref.Get("str"))
|
||||
r.Equal("42", pref.Get("int"))
|
||||
r.Equal("true", pref.Get("bool"))
|
||||
r.Equal("t", pref.Get("falseBool"))
|
||||
}
|
||||
|
||||
func TestKeyValueStoreGetInt(t *testing.T) {
|
||||
pref := newTestKeyValueStore(t)
|
||||
require.Equal(t, 0, pref.GetInt("str"))
|
||||
require.Equal(t, 42, pref.GetInt("int"))
|
||||
require.Equal(t, 0, pref.GetInt("bool"))
|
||||
require.Equal(t, 0, pref.GetInt("falseBool"))
|
||||
r := require.New(t)
|
||||
pref, clean := newTestKeyValueStore(r)
|
||||
defer clean()
|
||||
|
||||
r.Equal(0, pref.GetInt("str"))
|
||||
r.Equal(42, pref.GetInt("int"))
|
||||
r.Equal(0, pref.GetInt("bool"))
|
||||
r.Equal(0, pref.GetInt("falseBool"))
|
||||
}
|
||||
|
||||
func TestKeyValueStoreGetBool(t *testing.T) {
|
||||
pref := newTestKeyValueStore(t)
|
||||
require.Equal(t, false, pref.GetBool("str"))
|
||||
require.Equal(t, false, pref.GetBool("int"))
|
||||
require.Equal(t, true, pref.GetBool("bool"))
|
||||
require.Equal(t, false, pref.GetBool("falseBool"))
|
||||
r := require.New(t)
|
||||
pref, clean := newTestKeyValueStore(r)
|
||||
defer clean()
|
||||
|
||||
r.Equal(false, pref.GetBool("str"))
|
||||
r.Equal(false, pref.GetBool("int"))
|
||||
r.Equal(true, pref.GetBool("bool"))
|
||||
r.Equal(false, pref.GetBool("falseBool"))
|
||||
}
|
||||
|
||||
func TestKeyValueStoreSetDefault(t *testing.T) {
|
||||
pref := newTestEmptyKeyValueStore(t)
|
||||
r := require.New(t)
|
||||
pref, clean := newTestEmptyKeyValueStore(r)
|
||||
defer clean()
|
||||
|
||||
pref.setDefault("key", "value")
|
||||
pref.setDefault("key", "othervalue")
|
||||
require.Equal(t, "value", pref.Get("key"))
|
||||
r.Equal("value", pref.Get("key"))
|
||||
}
|
||||
|
||||
func TestKeyValueStoreSet(t *testing.T) {
|
||||
pref := newTestEmptyKeyValueStore(t)
|
||||
r := require.New(t)
|
||||
pref, clean := newTestEmptyKeyValueStore(r)
|
||||
defer clean()
|
||||
|
||||
pref.Set("str", "value")
|
||||
checkSavedKeyValueStore(t, "{\n\t\"str\": \"value\"\n}")
|
||||
checkSavedKeyValueStore(r, pref.path, "{\n\t\"str\": \"value\"\n}")
|
||||
}
|
||||
|
||||
func TestKeyValueStoreSetInt(t *testing.T) {
|
||||
pref := newTestEmptyKeyValueStore(t)
|
||||
r := require.New(t)
|
||||
pref, clean := newTestEmptyKeyValueStore(r)
|
||||
defer clean()
|
||||
|
||||
pref.SetInt("int", 42)
|
||||
checkSavedKeyValueStore(t, "{\n\t\"int\": \"42\"\n}")
|
||||
checkSavedKeyValueStore(r, pref.path, "{\n\t\"int\": \"42\"\n}")
|
||||
}
|
||||
|
||||
func TestKeyValueStoreSetBool(t *testing.T) {
|
||||
pref := newTestEmptyKeyValueStore(t)
|
||||
r := require.New(t)
|
||||
pref, clean := newTestEmptyKeyValueStore(r)
|
||||
defer clean()
|
||||
|
||||
pref.SetBool("trueBool", true)
|
||||
pref.SetBool("falseBool", false)
|
||||
checkSavedKeyValueStore(t, "{\n\t\"falseBool\": \"false\",\n\t\"trueBool\": \"true\"\n}")
|
||||
checkSavedKeyValueStore(r, pref.path, "{\n\t\"falseBool\": \"false\",\n\t\"trueBool\": \"true\"\n}")
|
||||
}
|
||||
|
||||
func newTestEmptyKeyValueStore(t *testing.T) *keyValueStore {
|
||||
require.NoError(t, os.RemoveAll(testPrefFilePath))
|
||||
return newKeyValueStore(testPrefFilePath)
|
||||
func newTmpFile(r *require.Assertions) (path string, clean func()) {
|
||||
tmpfile, err := ioutil.TempFile("", "pref.*.json")
|
||||
r.NoError(err)
|
||||
defer r.NoError(tmpfile.Close())
|
||||
|
||||
return tmpfile.Name(), func() {
|
||||
r.NoError(os.Remove(tmpfile.Name()))
|
||||
}
|
||||
}
|
||||
|
||||
func newTestKeyValueStore(t *testing.T) *keyValueStore {
|
||||
require.NoError(t, ioutil.WriteFile(testPrefFilePath, []byte("{\"str\":\"value\",\"int\":\"42\",\"bool\":\"true\",\"falseBool\":\"t\"}"), 0700))
|
||||
return newKeyValueStore(testPrefFilePath)
|
||||
func newTestEmptyKeyValueStore(r *require.Assertions) (*keyValueStore, func()) {
|
||||
path, clean := newTmpFile(r)
|
||||
return newKeyValueStore(path), clean
|
||||
}
|
||||
|
||||
func checkSavedKeyValueStore(t *testing.T, expected string) {
|
||||
data, err := ioutil.ReadFile(testPrefFilePath)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, expected, string(data))
|
||||
func newTestKeyValueStore(r *require.Assertions) (*keyValueStore, func()) {
|
||||
path, clean := newTmpFile(r)
|
||||
r.NoError(ioutil.WriteFile(path, []byte("{\"str\":\"value\",\"int\":\"42\",\"bool\":\"true\",\"falseBool\":\"t\"}"), 0700))
|
||||
return newKeyValueStore(path), clean
|
||||
}
|
||||
|
||||
func checkSavedKeyValueStore(r *require.Assertions, path, expected string) {
|
||||
data, err := ioutil.ReadFile(path)
|
||||
r.NoError(err)
|
||||
r.Equal(expected, string(data))
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2021 Proton Technologies AG
|
||||
// Copyright (c) 2022 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
@ -43,6 +43,17 @@ const (
|
||||
UpdateChannelKey = "update_channel"
|
||||
RolloutKey = "rollout"
|
||||
PreferredKeychainKey = "preferred_keychain"
|
||||
CacheEnabledKey = "cache_enabled"
|
||||
CacheCompressionKey = "cache_compression"
|
||||
CacheLocationKey = "cache_location"
|
||||
CacheMinFreeAbsKey = "cache_min_free_abs"
|
||||
CacheMinFreeRatKey = "cache_min_free_rat"
|
||||
CacheConcurrencyRead = "cache_concurrent_read"
|
||||
CacheConcurrencyWrite = "cache_concurrent_write"
|
||||
IMAPWorkers = "imap_workers"
|
||||
FetchWorkers = "fetch_workers"
|
||||
AttachmentWorkers = "attachment_workers"
|
||||
ColorScheme = "color_scheme"
|
||||
)
|
||||
|
||||
type Settings struct {
|
||||
@ -80,6 +91,17 @@ func (s *Settings) setDefaultValues() {
|
||||
s.setDefault(UpdateChannelKey, "")
|
||||
s.setDefault(RolloutKey, fmt.Sprintf("%v", rand.Float64())) //nolint[gosec] G404 It is OK to use weak random number generator here
|
||||
s.setDefault(PreferredKeychainKey, "")
|
||||
s.setDefault(CacheEnabledKey, "true")
|
||||
s.setDefault(CacheCompressionKey, "true")
|
||||
s.setDefault(CacheLocationKey, "")
|
||||
s.setDefault(CacheMinFreeAbsKey, "250000000")
|
||||
s.setDefault(CacheMinFreeRatKey, "")
|
||||
s.setDefault(CacheConcurrencyRead, "16")
|
||||
s.setDefault(CacheConcurrencyWrite, "16")
|
||||
s.setDefault(IMAPWorkers, "16")
|
||||
s.setDefault(FetchWorkers, "16")
|
||||
s.setDefault(AttachmentWorkers, "16")
|
||||
s.setDefault(ColorScheme, "")
|
||||
|
||||
s.setDefault(APIPortKey, DefaultAPIPort)
|
||||
s.setDefault(IMAPPortKey, DefaultIMAPPort)
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2021 Proton Technologies AG
|
||||
// Copyright (c) 2022 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2021 Proton Technologies AG
|
||||
// Copyright (c) 2022 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2021 Proton Technologies AG
|
||||
// Copyright (c) 2022 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2021 Proton Technologies AG
|
||||
// Copyright (c) 2022 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2021 Proton Technologies AG
|
||||
// Copyright (c) 2022 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2021 Proton Technologies AG
|
||||
// Copyright (c) 2022 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2021 Proton Technologies AG
|
||||
// Copyright (c) 2022 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2021 Proton Technologies AG
|
||||
// Copyright (c) 2022 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2021 Proton Technologies AG
|
||||
// Copyright (c) 2022 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2021 Proton Technologies AG
|
||||
// Copyright (c) 2022 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.Bridge.
|
||||
//
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2021 Proton Technologies AG
|
||||
// Copyright (c) 2022 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2021 Proton Technologies AG
|
||||
// Copyright (c) 2022 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2021 Proton Technologies AG
|
||||
// Copyright (c) 2022 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2021 Proton Technologies AG
|
||||
// Copyright (c) 2022 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2021 Proton Technologies AG
|
||||
// Copyright (c) 2022 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2021 Proton Technologies AG
|
||||
// Copyright (c) 2022 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2021 Proton Technologies AG
|
||||
// Copyright (c) 2022 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2021 Proton Technologies AG
|
||||
// Copyright (c) 2022 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
@ -41,6 +41,7 @@ const (
|
||||
NoActiveKeyForRecipientEvent = "noActiveKeyForRecipient"
|
||||
UpgradeApplicationEvent = "upgradeApplication"
|
||||
TLSCertIssue = "tlsCertPinningIssue"
|
||||
UserChangeDone = "QMLUserChangedDone"
|
||||
|
||||
// LogoutEventTimeout is the minimum time to permit between logout events being sent.
|
||||
LogoutEventTimeout = 3 * time.Minute
|
||||
@ -54,4 +55,6 @@ func SetupEvents(listener listener.Listener) {
|
||||
listener.SetBuffer(InternetOffEvent)
|
||||
listener.SetBuffer(UpgradeApplicationEvent)
|
||||
listener.SetBuffer(TLSCertIssue)
|
||||
listener.SetBuffer(UserRefreshEvent)
|
||||
listener.Book(UserChangeDone)
|
||||
}
|
||||
|
||||
11
internal/frontend/.gitignore
vendored
Normal file
11
internal/frontend/.gitignore
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
# Auto generated
|
||||
moc.cpp
|
||||
moc.go
|
||||
moc.h
|
||||
moc_cgo_*.go
|
||||
moc_moc.h
|
||||
rcc.cpp
|
||||
rcc.qrc
|
||||
rcc_cgo_*.go
|
||||
*.qmlc
|
||||
|
||||
14
internal/frontend/Makefile.local
Normal file
14
internal/frontend/Makefile.local
Normal file
@ -0,0 +1,14 @@
|
||||
FILES=$(shell find . -iname 'rcc.qrc')
|
||||
FILES+=$(shell find . -iname 'rcc.cpp')
|
||||
FILES+=$(shell find . -iname 'rcc_cgo*.go')
|
||||
|
||||
FILES+=$(shell find . -iname 'moc.go')
|
||||
FILES+=$(shell find . -iname 'moc.cpp')
|
||||
FILES+=$(shell find . -iname 'moc.h')
|
||||
FILES+=$(shell find . -iname 'moc_cgo*.go')
|
||||
|
||||
FILES+=$(shell find ./qml -iname '*.qmlc')
|
||||
|
||||
clean:
|
||||
rm -f ${FILES}
|
||||
|
||||
@ -1,100 +0,0 @@
|
||||
// Copyright (c) 2021 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
// ProtonMail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// ProtonMail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package cliie
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/internal/frontend/types"
|
||||
"github.com/abiosoft/ishell"
|
||||
)
|
||||
|
||||
// completeUsernames is a helper to complete usernames as the user types.
|
||||
func (f *frontendCLI) completeUsernames(args []string) (usernames []string) {
|
||||
if len(args) > 1 {
|
||||
return
|
||||
}
|
||||
arg := ""
|
||||
if len(args) == 1 {
|
||||
arg = args[0]
|
||||
}
|
||||
for _, user := range f.ie.GetUsers() {
|
||||
if strings.HasPrefix(strings.ToLower(user.Username()), strings.ToLower(arg)) {
|
||||
usernames = append(usernames, user.Username())
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// noAccountWrapper is a decorator for functions which need any account to be properly functional.
|
||||
func (f *frontendCLI) noAccountWrapper(callback func(*ishell.Context)) func(*ishell.Context) {
|
||||
return func(c *ishell.Context) {
|
||||
users := f.ie.GetUsers()
|
||||
if len(users) == 0 {
|
||||
f.Println("No active accounts. Please add account to continue.")
|
||||
} else {
|
||||
callback(c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (f *frontendCLI) askUserByIndexOrName(c *ishell.Context) types.User {
|
||||
user := f.getUserByIndexOrName("")
|
||||
if user != nil {
|
||||
return user
|
||||
}
|
||||
|
||||
numberOfAccounts := len(f.ie.GetUsers())
|
||||
indexRange := fmt.Sprintf("number between 0 and %d", numberOfAccounts-1)
|
||||
if len(c.Args) == 0 {
|
||||
f.Printf("Please choose %s or username.\n", indexRange)
|
||||
return nil
|
||||
}
|
||||
arg := c.Args[0]
|
||||
user = f.getUserByIndexOrName(arg)
|
||||
if user == nil {
|
||||
f.Printf("Wrong input '%s'. Choose %s or username.\n", bold(arg), indexRange)
|
||||
return nil
|
||||
}
|
||||
return user
|
||||
}
|
||||
|
||||
func (f *frontendCLI) getUserByIndexOrName(arg string) types.User {
|
||||
users := f.ie.GetUsers()
|
||||
numberOfAccounts := len(users)
|
||||
if numberOfAccounts == 0 {
|
||||
return nil
|
||||
}
|
||||
if numberOfAccounts == 1 {
|
||||
return users[0]
|
||||
}
|
||||
if index, err := strconv.Atoi(arg); err == nil {
|
||||
if index < 0 || index >= numberOfAccounts {
|
||||
return nil
|
||||
}
|
||||
return users[index]
|
||||
}
|
||||
for _, user := range users {
|
||||
if user.Username() == arg {
|
||||
return user
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -1,154 +0,0 @@
|
||||
// Copyright (c) 2021 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
// ProtonMail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// ProtonMail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package cliie
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/abiosoft/ishell"
|
||||
)
|
||||
|
||||
func (f *frontendCLI) listAccounts(c *ishell.Context) {
|
||||
spacing := "%-2d: %-20s (%-15s, %-15s)\n"
|
||||
f.Printf(bold(strings.ReplaceAll(spacing, "d", "s")), "#", "account", "status", "address mode")
|
||||
for idx, user := range f.ie.GetUsers() {
|
||||
connected := "disconnected"
|
||||
if user.IsConnected() {
|
||||
connected = "connected"
|
||||
}
|
||||
mode := "split"
|
||||
if user.IsCombinedAddressMode() {
|
||||
mode = "combined"
|
||||
}
|
||||
f.Printf(spacing, idx, user.Username(), connected, mode)
|
||||
}
|
||||
f.Println()
|
||||
}
|
||||
|
||||
func (f *frontendCLI) loginAccount(c *ishell.Context) { // nolint[funlen]
|
||||
f.ShowPrompt(false)
|
||||
defer f.ShowPrompt(true)
|
||||
|
||||
loginName := ""
|
||||
if len(c.Args) > 0 {
|
||||
user := f.getUserByIndexOrName(c.Args[0])
|
||||
if user != nil {
|
||||
loginName = user.GetPrimaryAddress()
|
||||
}
|
||||
}
|
||||
|
||||
if loginName == "" {
|
||||
loginName = f.readStringInAttempts("Username", c.ReadLine, isNotEmpty)
|
||||
if loginName == "" {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
f.Println("Username:", loginName)
|
||||
}
|
||||
|
||||
password := f.readStringInAttempts("Password", c.ReadPassword, isNotEmpty)
|
||||
if password == "" {
|
||||
return
|
||||
}
|
||||
|
||||
f.Println("Authenticating ... ")
|
||||
client, auth, err := f.ie.Login(loginName, password)
|
||||
if err != nil {
|
||||
f.processAPIError(err)
|
||||
return
|
||||
}
|
||||
|
||||
if auth.HasTwoFactor() {
|
||||
twoFactor := f.readStringInAttempts("Two factor code", c.ReadLine, isNotEmpty)
|
||||
if twoFactor == "" {
|
||||
return
|
||||
}
|
||||
|
||||
err = client.Auth2FA(context.Background(), twoFactor)
|
||||
if err != nil {
|
||||
f.processAPIError(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
mailboxPassword := password
|
||||
if auth.HasMailboxPassword() {
|
||||
mailboxPassword = f.readStringInAttempts("Mailbox password", c.ReadPassword, isNotEmpty)
|
||||
}
|
||||
if mailboxPassword == "" {
|
||||
return
|
||||
}
|
||||
|
||||
f.Println("Adding account ...")
|
||||
user, err := f.ie.FinishLogin(client, auth, mailboxPassword)
|
||||
if err != nil {
|
||||
log.WithField("username", loginName).WithError(err).Error("Login was unsuccessful")
|
||||
f.Println("Adding account was unsuccessful:", err)
|
||||
return
|
||||
}
|
||||
|
||||
f.Printf("Account %s was added successfully.\n", bold(user.Username()))
|
||||
}
|
||||
|
||||
func (f *frontendCLI) logoutAccount(c *ishell.Context) {
|
||||
f.ShowPrompt(false)
|
||||
defer f.ShowPrompt(true)
|
||||
|
||||
user := f.askUserByIndexOrName(c)
|
||||
if user == nil {
|
||||
return
|
||||
}
|
||||
if f.yesNoQuestion("Are you sure you want to logout account " + bold(user.Username())) {
|
||||
if err := user.Logout(); err != nil {
|
||||
f.printAndLogError("Logging out failed: ", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (f *frontendCLI) deleteAccount(c *ishell.Context) {
|
||||
f.ShowPrompt(false)
|
||||
defer f.ShowPrompt(true)
|
||||
|
||||
user := f.askUserByIndexOrName(c)
|
||||
if user == nil {
|
||||
return
|
||||
}
|
||||
if f.yesNoQuestion("Are you sure you want to " + bold("remove account "+user.Username())) {
|
||||
clearCache := f.yesNoQuestion("Do you want to remove cache for this account")
|
||||
if err := f.ie.DeleteUser(user.ID(), clearCache); err != nil {
|
||||
f.printAndLogError("Cannot delete account: ", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (f *frontendCLI) deleteAccounts(c *ishell.Context) {
|
||||
f.ShowPrompt(false)
|
||||
defer f.ShowPrompt(true)
|
||||
|
||||
if !f.yesNoQuestion("Do you really want remove all accounts") {
|
||||
return
|
||||
}
|
||||
for _, user := range f.ie.GetUsers() {
|
||||
if err := f.ie.DeleteUser(user.ID(), false); err != nil {
|
||||
f.printAndLogError("Cannot delete account ", user.Username(), ": ", err)
|
||||
}
|
||||
}
|
||||
c.Println("Keychain cleared")
|
||||
}
|
||||
@ -1,224 +0,0 @@
|
||||
// Copyright (c) 2021 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
// ProtonMail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// ProtonMail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
// Package cliie provides CLI interface of the Import-Export app.
|
||||
package cliie
|
||||
|
||||
import (
|
||||
"github.com/ProtonMail/proton-bridge/internal/events"
|
||||
"github.com/ProtonMail/proton-bridge/internal/frontend/types"
|
||||
"github.com/ProtonMail/proton-bridge/internal/locations"
|
||||
"github.com/ProtonMail/proton-bridge/internal/updater"
|
||||
"github.com/ProtonMail/proton-bridge/pkg/listener"
|
||||
|
||||
"github.com/abiosoft/ishell"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var (
|
||||
log = logrus.WithField("pkg", "frontend/cli-ie") //nolint[gochecknoglobals]
|
||||
)
|
||||
|
||||
type frontendCLI struct {
|
||||
*ishell.Shell
|
||||
|
||||
locations *locations.Locations
|
||||
eventListener listener.Listener
|
||||
updater types.Updater
|
||||
ie types.ImportExporter
|
||||
|
||||
restarter types.Restarter
|
||||
}
|
||||
|
||||
// New returns a new CLI frontend configured with the given options.
|
||||
func New( //nolint[funlen]
|
||||
panicHandler types.PanicHandler,
|
||||
|
||||
locations *locations.Locations,
|
||||
eventListener listener.Listener,
|
||||
updater types.Updater,
|
||||
ie types.ImportExporter,
|
||||
restarter types.Restarter,
|
||||
) *frontendCLI { //nolint[golint]
|
||||
fe := &frontendCLI{
|
||||
Shell: ishell.New(),
|
||||
|
||||
locations: locations,
|
||||
eventListener: eventListener,
|
||||
updater: updater,
|
||||
ie: ie,
|
||||
|
||||
restarter: restarter,
|
||||
}
|
||||
|
||||
// Clear commands.
|
||||
clearCmd := &ishell.Cmd{Name: "clear",
|
||||
Help: "remove stored accounts and preferences. (alias: cl)",
|
||||
Aliases: []string{"cl"},
|
||||
}
|
||||
clearCmd.AddCmd(&ishell.Cmd{Name: "accounts",
|
||||
Help: "remove all accounts from keychain. (aliases: a, k, keychain)",
|
||||
Aliases: []string{"a", "k", "keychain"},
|
||||
Func: fe.deleteAccounts,
|
||||
})
|
||||
fe.AddCmd(clearCmd)
|
||||
|
||||
// Check commands.
|
||||
checkCmd := &ishell.Cmd{Name: "check", Help: "check internet connection or new version."}
|
||||
checkCmd.AddCmd(&ishell.Cmd{Name: "updates",
|
||||
Help: "check for Import-Export updates. (aliases: u, v, version)",
|
||||
Aliases: []string{"u", "version", "v"},
|
||||
Func: fe.checkUpdates,
|
||||
})
|
||||
fe.AddCmd(checkCmd)
|
||||
|
||||
// Print info commands.
|
||||
fe.AddCmd(&ishell.Cmd{Name: "log-dir",
|
||||
Help: "print path to directory with logs. (aliases: log, logs)",
|
||||
Aliases: []string{"log", "logs"},
|
||||
Func: fe.printLogDir,
|
||||
})
|
||||
fe.AddCmd(&ishell.Cmd{Name: "manual",
|
||||
Help: "print URL with instructions. (alias: man)",
|
||||
Aliases: []string{"man"},
|
||||
Func: fe.printManual,
|
||||
})
|
||||
fe.AddCmd(&ishell.Cmd{Name: "credits",
|
||||
Help: "print used resources.",
|
||||
Func: fe.printCredits,
|
||||
})
|
||||
|
||||
// Account commands.
|
||||
fe.AddCmd(&ishell.Cmd{Name: "list",
|
||||
Help: "print the list of accounts. (aliases: l, ls)",
|
||||
Func: fe.noAccountWrapper(fe.listAccounts),
|
||||
Aliases: []string{"l", "ls"},
|
||||
})
|
||||
fe.AddCmd(&ishell.Cmd{Name: "login",
|
||||
Help: "login procedure to add or connect account. Optionally use index or account as parameter. (aliases: a, add, con, connect)",
|
||||
Func: fe.loginAccount,
|
||||
Aliases: []string{"add", "a", "con", "connect"},
|
||||
Completer: fe.completeUsernames,
|
||||
})
|
||||
fe.AddCmd(&ishell.Cmd{Name: "logout",
|
||||
Help: "disconnect the account. Use index or account name as parameter. (aliases: d, disconnect)",
|
||||
Func: fe.noAccountWrapper(fe.logoutAccount),
|
||||
Aliases: []string{"d", "disconnect"},
|
||||
Completer: fe.completeUsernames,
|
||||
})
|
||||
fe.AddCmd(&ishell.Cmd{Name: "delete",
|
||||
Help: "remove the account from keychain. Use index or account name as parameter. (aliases: del, rm, remove)",
|
||||
Func: fe.noAccountWrapper(fe.deleteAccount),
|
||||
Aliases: []string{"del", "rm", "remove"},
|
||||
Completer: fe.completeUsernames,
|
||||
})
|
||||
|
||||
// Import-Export commands.
|
||||
importCmd := &ishell.Cmd{Name: "import",
|
||||
Help: "import messages. (alias: imp)",
|
||||
Aliases: []string{"imp"},
|
||||
}
|
||||
importCmd.AddCmd(&ishell.Cmd{Name: "local",
|
||||
Help: "import local messages. (aliases: loc)",
|
||||
Func: fe.noAccountWrapper(fe.importLocalMessages),
|
||||
Aliases: []string{"loc"},
|
||||
})
|
||||
importCmd.AddCmd(&ishell.Cmd{Name: "remote",
|
||||
Help: "import remote messages. (aliases: rem)",
|
||||
Func: fe.noAccountWrapper(fe.importRemoteMessages),
|
||||
Aliases: []string{"rem"},
|
||||
})
|
||||
fe.AddCmd(importCmd)
|
||||
|
||||
exportCmd := &ishell.Cmd{Name: "export",
|
||||
Help: "export messages. (alias: exp)",
|
||||
Aliases: []string{"exp"},
|
||||
}
|
||||
exportCmd.AddCmd(&ishell.Cmd{Name: "eml",
|
||||
Help: "export messages to eml files.",
|
||||
Func: fe.noAccountWrapper(fe.exportMessagesToEML),
|
||||
})
|
||||
exportCmd.AddCmd(&ishell.Cmd{Name: "mbox",
|
||||
Help: "export messages to mbox files.",
|
||||
Func: fe.noAccountWrapper(fe.exportMessagesToMBOX),
|
||||
})
|
||||
fe.AddCmd(exportCmd)
|
||||
|
||||
// System commands.
|
||||
fe.AddCmd(&ishell.Cmd{Name: "restart",
|
||||
Help: "restart the Import-Export app.",
|
||||
Func: fe.restart,
|
||||
})
|
||||
|
||||
go func() {
|
||||
defer panicHandler.HandlePanic()
|
||||
fe.watchEvents()
|
||||
}()
|
||||
return fe
|
||||
}
|
||||
|
||||
func (f *frontendCLI) watchEvents() {
|
||||
errorCh := f.eventListener.ProvideChannel(events.ErrorEvent)
|
||||
credentialsErrorCh := f.eventListener.ProvideChannel(events.CredentialsErrorEvent)
|
||||
internetOffCh := f.eventListener.ProvideChannel(events.InternetOffEvent)
|
||||
internetOnCh := f.eventListener.ProvideChannel(events.InternetOnEvent)
|
||||
addressChangedLogoutCh := f.eventListener.ProvideChannel(events.AddressChangedLogoutEvent)
|
||||
logoutCh := f.eventListener.ProvideChannel(events.LogoutEvent)
|
||||
certIssue := f.eventListener.ProvideChannel(events.TLSCertIssue)
|
||||
for {
|
||||
select {
|
||||
case errorDetails := <-errorCh:
|
||||
f.Println("Import-Export failed:", errorDetails)
|
||||
case <-credentialsErrorCh:
|
||||
f.notifyCredentialsError()
|
||||
case <-internetOffCh:
|
||||
f.notifyInternetOff()
|
||||
case <-internetOnCh:
|
||||
f.notifyInternetOn()
|
||||
case address := <-addressChangedLogoutCh:
|
||||
f.notifyLogout(address)
|
||||
case userID := <-logoutCh:
|
||||
user, err := f.ie.GetUser(userID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
f.notifyLogout(user.Username())
|
||||
case <-certIssue:
|
||||
f.notifyCertIssue()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Loop starts the frontend loop with an interactive shell.
|
||||
func (f *frontendCLI) Loop() error {
|
||||
f.Print(`
|
||||
Welcome to ProtonMail Import-Export app interactive shell
|
||||
|
||||
WARNING: The CLI is an experimental feature and does not yet cover all functionality.
|
||||
`)
|
||||
f.Run()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *frontendCLI) NotifyManualUpdate(update updater.VersionInfo, canInstall bool) {
|
||||
// NOTE: Save the update somewhere so that it can be installed when user chooses "install now".
|
||||
}
|
||||
|
||||
func (f *frontendCLI) WaitUntilFrontendIsReady() {}
|
||||
func (f *frontendCLI) SetVersion(version updater.VersionInfo) {}
|
||||
func (f *frontendCLI) NotifySilentUpdateInstalled() {}
|
||||
func (f *frontendCLI) NotifySilentUpdateError(err error) {}
|
||||
@ -1,232 +0,0 @@
|
||||
// Copyright (c) 2021 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
// ProtonMail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// ProtonMail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package cliie
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/internal/frontend/types"
|
||||
"github.com/ProtonMail/proton-bridge/internal/transfer"
|
||||
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
||||
"github.com/abiosoft/ishell"
|
||||
)
|
||||
|
||||
func (f *frontendCLI) importLocalMessages(c *ishell.Context) {
|
||||
f.ShowPrompt(false)
|
||||
defer f.ShowPrompt(true)
|
||||
|
||||
user, path := f.getUserAndPath(c, false)
|
||||
if user == nil || path == "" {
|
||||
return
|
||||
}
|
||||
|
||||
t, err := f.ie.GetLocalImporter(user.Username(), user.GetPrimaryAddress(), path)
|
||||
f.transfer(t, err, false, true)
|
||||
}
|
||||
|
||||
func (f *frontendCLI) importRemoteMessages(c *ishell.Context) {
|
||||
f.ShowPrompt(false)
|
||||
defer f.ShowPrompt(true)
|
||||
|
||||
user := f.askUserByIndexOrName(c)
|
||||
if user == nil {
|
||||
return
|
||||
}
|
||||
|
||||
username := f.readStringInAttempts("IMAP username", c.ReadLine, isNotEmpty)
|
||||
if username == "" {
|
||||
return
|
||||
}
|
||||
password := f.readStringInAttempts("IMAP password", c.ReadPassword, isNotEmpty)
|
||||
if password == "" {
|
||||
return
|
||||
}
|
||||
host := f.readStringInAttempts("IMAP host", c.ReadLine, isNotEmpty)
|
||||
if host == "" {
|
||||
return
|
||||
}
|
||||
port := f.readStringInAttempts("IMAP port", c.ReadLine, isNotEmpty)
|
||||
if port == "" {
|
||||
return
|
||||
}
|
||||
|
||||
t, err := f.ie.GetRemoteImporter(user.Username(), user.GetPrimaryAddress(), username, password, host, port)
|
||||
f.transfer(t, err, false, true)
|
||||
}
|
||||
|
||||
func (f *frontendCLI) exportMessagesToEML(c *ishell.Context) {
|
||||
f.ShowPrompt(false)
|
||||
defer f.ShowPrompt(true)
|
||||
|
||||
user, path := f.getUserAndPath(c, true)
|
||||
if user == nil || path == "" {
|
||||
return
|
||||
}
|
||||
|
||||
t, err := f.ie.GetEMLExporter(user.Username(), user.GetPrimaryAddress(), path)
|
||||
f.transfer(t, err, true, false)
|
||||
}
|
||||
|
||||
func (f *frontendCLI) exportMessagesToMBOX(c *ishell.Context) {
|
||||
f.ShowPrompt(false)
|
||||
defer f.ShowPrompt(true)
|
||||
|
||||
user, path := f.getUserAndPath(c, true)
|
||||
if user == nil || path == "" {
|
||||
return
|
||||
}
|
||||
|
||||
t, err := f.ie.GetMBOXExporter(user.Username(), user.GetPrimaryAddress(), path)
|
||||
f.transfer(t, err, true, false)
|
||||
}
|
||||
|
||||
func (f *frontendCLI) getUserAndPath(c *ishell.Context, createPath bool) (types.User, string) {
|
||||
user := f.askUserByIndexOrName(c)
|
||||
if user == nil {
|
||||
return nil, ""
|
||||
}
|
||||
|
||||
path := f.readStringInAttempts("Path of EML and MBOX files", c.ReadLine, isNotEmpty)
|
||||
if path == "" {
|
||||
return nil, ""
|
||||
}
|
||||
|
||||
if createPath {
|
||||
_ = os.Mkdir(path, os.ModePerm)
|
||||
}
|
||||
|
||||
return user, path
|
||||
}
|
||||
|
||||
func (f *frontendCLI) transfer(t *transfer.Transfer, err error, askSkipEncrypted bool, askGlobalMailbox bool) {
|
||||
if err != nil {
|
||||
f.printAndLogError("Failed to init transferrer: ", err)
|
||||
return
|
||||
}
|
||||
|
||||
if askSkipEncrypted {
|
||||
skipEncryptedMessages := f.yesNoQuestion("Skip encrypted messages")
|
||||
t.SetSkipEncryptedMessages(skipEncryptedMessages)
|
||||
}
|
||||
|
||||
if !f.setTransferRules(t) {
|
||||
return
|
||||
}
|
||||
|
||||
if askGlobalMailbox {
|
||||
if err := f.setTransferGlobalMailbox(t); err != nil {
|
||||
f.printAndLogError("Failed to create global mailbox: ", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
progress := t.Start()
|
||||
for range progress.GetUpdateChannel() {
|
||||
f.printTransferProgress(progress)
|
||||
}
|
||||
f.printTransferResult(progress)
|
||||
}
|
||||
|
||||
func (f *frontendCLI) setTransferGlobalMailbox(t *transfer.Transfer) error {
|
||||
labelName := fmt.Sprintf("Imported %s", time.Now().Format("Jan-02-2006 15:04"))
|
||||
|
||||
useGlobalLabel := f.yesNoQuestion("Use global label " + labelName)
|
||||
if !useGlobalLabel {
|
||||
return nil
|
||||
}
|
||||
|
||||
globalMailbox, err := t.CreateTargetMailbox(transfer.Mailbox{
|
||||
Name: labelName,
|
||||
Color: pmapi.LabelColors[0],
|
||||
IsExclusive: false,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
t.SetGlobalMailbox(&globalMailbox)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *frontendCLI) setTransferRules(t *transfer.Transfer) bool {
|
||||
f.Println("Rules:")
|
||||
for _, rule := range t.GetRules() {
|
||||
if !rule.Active {
|
||||
continue
|
||||
}
|
||||
targets := strings.Join(rule.TargetMailboxNames(), ", ")
|
||||
if rule.HasTimeLimit() {
|
||||
f.Printf(" %-30s → %s (%s - %s)\n", rule.SourceMailbox.Name, targets, rule.FromDate(), rule.ToDate())
|
||||
} else {
|
||||
f.Printf(" %-30s → %s\n", rule.SourceMailbox.Name, targets)
|
||||
}
|
||||
}
|
||||
|
||||
return f.yesNoQuestion("Proceed")
|
||||
}
|
||||
|
||||
func (f *frontendCLI) printTransferProgress(progress *transfer.Progress) {
|
||||
counts := progress.GetCounts()
|
||||
if counts.Total != 0 {
|
||||
f.Println(fmt.Sprintf(
|
||||
"Progress update: %d (%d / %d) / %d, skipped: %d, failed: %d",
|
||||
counts.Imported,
|
||||
counts.Exported,
|
||||
counts.Added,
|
||||
counts.Total,
|
||||
counts.Skipped,
|
||||
counts.Failed,
|
||||
))
|
||||
}
|
||||
|
||||
if progress.IsPaused() {
|
||||
f.Printf("Transfer is paused bacause %s", progress.PauseReason())
|
||||
if !f.yesNoQuestion("Continue (y) or stop (n)") {
|
||||
progress.Stop()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (f *frontendCLI) printTransferResult(progress *transfer.Progress) {
|
||||
err := progress.GetFatalError()
|
||||
if err != nil {
|
||||
f.Println("Transfer failed: " + err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
statuses := progress.GetFailedMessages()
|
||||
if len(statuses) == 0 {
|
||||
f.Println("Transfer finished!")
|
||||
return
|
||||
}
|
||||
|
||||
f.Println("Transfer finished with errors:")
|
||||
for _, messageStatus := range statuses {
|
||||
f.Printf(
|
||||
" %-17s | %-30s | %-30s\n %s: %s\n",
|
||||
messageStatus.Time.Format("Jan 02 2006 15:04"),
|
||||
messageStatus.From,
|
||||
messageStatus.Subject,
|
||||
messageStatus.SourceID,
|
||||
messageStatus.GetErrorMessage(),
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -1,42 +0,0 @@
|
||||
// Copyright (c) 2021 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
// ProtonMail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// ProtonMail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package cliie
|
||||
|
||||
import (
|
||||
"github.com/abiosoft/ishell"
|
||||
)
|
||||
|
||||
func (f *frontendCLI) restart(c *ishell.Context) {
|
||||
if f.yesNoQuestion("Are you sure you want to restart the Import-Export app") {
|
||||
f.Println("Restarting the Import-Export app...")
|
||||
f.restarter.SetToRestart()
|
||||
f.Stop()
|
||||
}
|
||||
}
|
||||
|
||||
func (f *frontendCLI) printLogDir(c *ishell.Context) {
|
||||
if path, err := f.locations.ProvideLogsPath(); err != nil {
|
||||
f.Println("Failed to determine location of log files")
|
||||
} else {
|
||||
f.Println("Log files are stored in\n\n ", path)
|
||||
}
|
||||
}
|
||||
|
||||
func (f *frontendCLI) printManual(c *ishell.Context) {
|
||||
f.Println("More instructions about the Import-Export app can be found at\n\n https://protonmail.com/support/categories/import-export/")
|
||||
}
|
||||
@ -1,128 +0,0 @@
|
||||
// Copyright (c) 2021 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
// ProtonMail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// ProtonMail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package cliie
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
||||
"github.com/fatih/color"
|
||||
)
|
||||
|
||||
const (
|
||||
maxInputRepeat = 2
|
||||
)
|
||||
|
||||
var (
|
||||
bold = color.New(color.Bold).SprintFunc() //nolint[gochecknoglobals]
|
||||
)
|
||||
|
||||
func isNotEmpty(val string) bool {
|
||||
return val != ""
|
||||
}
|
||||
|
||||
func (f *frontendCLI) yesNoQuestion(question string) bool {
|
||||
f.Print(question, "? yes/"+bold("no")+": ")
|
||||
yes := "yes"
|
||||
answer := strings.ToLower(f.ReadLine())
|
||||
for i := 0; i < len(answer); i++ {
|
||||
if i >= len(yes) || answer[i] != yes[i] {
|
||||
return false // Everything else is false.
|
||||
}
|
||||
}
|
||||
return len(answer) > 0 // Empty is false.
|
||||
}
|
||||
|
||||
func (f *frontendCLI) readStringInAttempts(title string, readFunc func() string, isOK func(string) bool) (value string) {
|
||||
f.Printf("%s: ", title)
|
||||
value = readFunc()
|
||||
title = strings.ToLower(string(title[0])) + title[1:]
|
||||
for i := 0; !isOK(value); i++ {
|
||||
if i >= maxInputRepeat {
|
||||
f.Println("Too many attempts")
|
||||
return ""
|
||||
}
|
||||
f.Printf("Please fill %s: ", title)
|
||||
value = readFunc()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (f *frontendCLI) printAndLogError(args ...interface{}) {
|
||||
log.Error(args...)
|
||||
f.Println(args...)
|
||||
}
|
||||
|
||||
func (f *frontendCLI) processAPIError(err error) {
|
||||
log.Warn("API error: ", err)
|
||||
switch err {
|
||||
case pmapi.ErrNoConnection:
|
||||
f.notifyInternetOff()
|
||||
case pmapi.ErrUpgradeApplication:
|
||||
f.notifyNeedUpgrade()
|
||||
default:
|
||||
f.Println("Server error:", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func (f *frontendCLI) notifyInternetOff() {
|
||||
f.Println("Internet connection is not available.")
|
||||
}
|
||||
|
||||
func (f *frontendCLI) notifyInternetOn() {
|
||||
f.Println("Internet connection is available again.")
|
||||
}
|
||||
|
||||
func (f *frontendCLI) notifyLogout(address string) {
|
||||
f.Printf("Account %s is disconnected. Login to continue using this account with email client.", address)
|
||||
}
|
||||
|
||||
func (f *frontendCLI) notifyNeedUpgrade() {
|
||||
version, err := f.updater.Check()
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Failed to notify need upgrade")
|
||||
return
|
||||
}
|
||||
f.Println("Please download and install the newest version of application from", version.LandingPage)
|
||||
}
|
||||
|
||||
func (f *frontendCLI) notifyCredentialsError() { // nolint[unused]
|
||||
// Print in 80-column width.
|
||||
f.Println("ProtonMail Import-Export app is not able to detect a supported password manager")
|
||||
f.Println("(pass, gnome-keyring). Please install and set up a supported password manager")
|
||||
f.Println("and restart the application.")
|
||||
}
|
||||
|
||||
func (f *frontendCLI) notifyCertIssue() {
|
||||
// Print in 80-column width.
|
||||
f.Println(`Connection security error: Your network connection to Proton services may
|
||||
be insecure.
|
||||
|
||||
Description:
|
||||
ProtonMail Import-Export was not able to establish a secure connection to Proton
|
||||
servers due to a TLS certificate error. This means your connection may
|
||||
potentially be insecure and susceptible to monitoring by third parties.
|
||||
|
||||
Recommendation:
|
||||
* If you trust your network operator, you can continue to use ProtonMail
|
||||
as usual.
|
||||
* If you don't trust your network operator, reconnect to ProtonMail over a VPN
|
||||
(such as ProtonVPN) which encrypts your Internet connection, or use
|
||||
a different network to access ProtonMail.
|
||||
`)
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2021 Proton Technologies AG
|
||||
// Copyright (c) 2022 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2021 Proton Technologies AG
|
||||
// Copyright (c) 2022 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
@ -115,7 +115,7 @@ func (f *frontendCLI) loginAccount(c *ishell.Context) { // nolint[funlen]
|
||||
}
|
||||
|
||||
f.Println("Authenticating ... ")
|
||||
client, auth, err := f.bridge.Login(loginName, password)
|
||||
client, auth, err := f.bridge.Login(loginName, []byte(password))
|
||||
if err != nil {
|
||||
f.processAPIError(err)
|
||||
return
|
||||
@ -143,7 +143,7 @@ func (f *frontendCLI) loginAccount(c *ishell.Context) { // nolint[funlen]
|
||||
}
|
||||
|
||||
f.Println("Adding account ...")
|
||||
user, err := f.bridge.FinishLogin(client, auth, mailboxPassword)
|
||||
user, err := f.bridge.FinishLogin(client, auth, []byte(mailboxPassword))
|
||||
if err != nil {
|
||||
log.WithField("username", loginName).WithError(err).Error("Login was unsuccessful")
|
||||
f.Println("Adding account was unsuccessful:", err)
|
||||
@ -192,14 +192,34 @@ func (f *frontendCLI) deleteAccounts(c *ishell.Context) {
|
||||
if !f.yesNoQuestion("Do you really want remove all accounts") {
|
||||
return
|
||||
}
|
||||
|
||||
for _, user := range f.bridge.GetUsers() {
|
||||
if err := f.bridge.DeleteUser(user.ID(), false); err != nil {
|
||||
f.printAndLogError("Cannot delete account ", user.Username(), ": ", err)
|
||||
}
|
||||
}
|
||||
|
||||
c.Println("Keychain cleared")
|
||||
}
|
||||
|
||||
func (f *frontendCLI) deleteEverything(c *ishell.Context) {
|
||||
f.ShowPrompt(false)
|
||||
defer f.ShowPrompt(true)
|
||||
|
||||
if !f.yesNoQuestion("Do you really want remove everything") {
|
||||
return
|
||||
}
|
||||
|
||||
f.bridge.FactoryReset()
|
||||
|
||||
c.Println("Everything cleared")
|
||||
|
||||
// Clearing data removes everything (db, preferences, ...) so everything has to be stopped and started again.
|
||||
f.restarter.SetToRestart()
|
||||
|
||||
f.Stop()
|
||||
}
|
||||
|
||||
func (f *frontendCLI) changeMode(c *ishell.Context) {
|
||||
user := f.askUserByIndexOrName(c)
|
||||
if user == nil {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2021 Proton Technologies AG
|
||||
// Copyright (c) 2022 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
@ -84,6 +84,11 @@ func New( //nolint[funlen]
|
||||
Aliases: []string{"a", "k", "keychain"},
|
||||
Func: fe.deleteAccounts,
|
||||
})
|
||||
clearCmd.AddCmd(&ishell.Cmd{Name: "everything",
|
||||
Help: "remove everything",
|
||||
Aliases: []string{"a", "k", "keychain"},
|
||||
Func: fe.deleteEverything,
|
||||
})
|
||||
fe.AddCmd(clearCmd)
|
||||
|
||||
// Change commands.
|
||||
@ -123,6 +128,24 @@ func New( //nolint[funlen]
|
||||
})
|
||||
fe.AddCmd(dohCmd)
|
||||
|
||||
// Cache-On-Disk commands.
|
||||
codCmd := &ishell.Cmd{Name: "local-cache",
|
||||
Help: "manage the local encrypted message cache",
|
||||
}
|
||||
codCmd.AddCmd(&ishell.Cmd{Name: "enable",
|
||||
Help: "enable the local cache",
|
||||
Func: fe.enableCacheOnDisk,
|
||||
})
|
||||
codCmd.AddCmd(&ishell.Cmd{Name: "disable",
|
||||
Help: "disable the local cache",
|
||||
Func: fe.disableCacheOnDisk,
|
||||
})
|
||||
codCmd.AddCmd(&ishell.Cmd{Name: "change-location",
|
||||
Help: "change the location of the local cache",
|
||||
Func: fe.setCacheOnDiskLocation,
|
||||
})
|
||||
fe.AddCmd(codCmd)
|
||||
|
||||
// Updates commands.
|
||||
updatesCmd := &ishell.Cmd{Name: "updates",
|
||||
Help: "manage bridge updates",
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2021 Proton Technologies AG
|
||||
// Copyright (c) 2022 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
@ -19,6 +19,7 @@ package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
@ -58,14 +59,17 @@ func (f *frontendCLI) deleteCache(c *ishell.Context) {
|
||||
if !f.yesNoQuestion("Do you really want to remove all stored preferences") {
|
||||
return
|
||||
}
|
||||
|
||||
if err := f.bridge.ClearData(); err != nil {
|
||||
f.printAndLogError("Cache clear failed: ", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
f.Println("Cached cleared, restarting bridge")
|
||||
// Clearing data removes everything (db, preferences, ...)
|
||||
// so everything has to be stopped and started again.
|
||||
|
||||
// Clearing data removes everything (db, preferences, ...) so everything has to be stopped and started again.
|
||||
f.restarter.SetToRestart()
|
||||
|
||||
f.Stop()
|
||||
}
|
||||
|
||||
@ -125,7 +129,7 @@ func (f *frontendCLI) changePort(c *ishell.Context) {
|
||||
}
|
||||
|
||||
func (f *frontendCLI) allowProxy(c *ishell.Context) {
|
||||
if f.settings.GetBool(settings.AllowProxyKey) {
|
||||
if f.bridge.GetProxyAllowed() {
|
||||
f.Println("Bridge is already set to use alternative routing to connect to Proton if it is being blocked.")
|
||||
return
|
||||
}
|
||||
@ -133,13 +137,12 @@ func (f *frontendCLI) allowProxy(c *ishell.Context) {
|
||||
f.Println("Bridge is currently set to NOT use alternative routing to connect to Proton if it is being blocked.")
|
||||
|
||||
if f.yesNoQuestion("Are you sure you want to allow bridge to do this") {
|
||||
f.settings.SetBool(settings.AllowProxyKey, true)
|
||||
f.bridge.AllowProxy()
|
||||
f.bridge.SetProxyAllowed(true)
|
||||
}
|
||||
}
|
||||
|
||||
func (f *frontendCLI) disallowProxy(c *ishell.Context) {
|
||||
if !f.settings.GetBool(settings.AllowProxyKey) {
|
||||
if !f.bridge.GetProxyAllowed() {
|
||||
f.Println("Bridge is already set to NOT use alternative routing to connect to Proton if it is being blocked.")
|
||||
return
|
||||
}
|
||||
@ -147,8 +150,62 @@ func (f *frontendCLI) disallowProxy(c *ishell.Context) {
|
||||
f.Println("Bridge is currently set to use alternative routing to connect to Proton if it is being blocked.")
|
||||
|
||||
if f.yesNoQuestion("Are you sure you want to stop bridge from doing this") {
|
||||
f.settings.SetBool(settings.AllowProxyKey, false)
|
||||
f.bridge.DisallowProxy()
|
||||
f.bridge.SetProxyAllowed(false)
|
||||
}
|
||||
}
|
||||
|
||||
func (f *frontendCLI) enableCacheOnDisk(c *ishell.Context) {
|
||||
if f.settings.GetBool(settings.CacheEnabledKey) {
|
||||
f.Println("The local cache is already enabled.")
|
||||
return
|
||||
}
|
||||
|
||||
if f.yesNoQuestion("Are you sure you want to enable the local cache") {
|
||||
if err := f.bridge.EnableCache(); err != nil {
|
||||
f.Println("The local cache could not be enabled.")
|
||||
return
|
||||
}
|
||||
|
||||
f.restarter.SetToRestart()
|
||||
f.Stop()
|
||||
}
|
||||
}
|
||||
|
||||
func (f *frontendCLI) disableCacheOnDisk(c *ishell.Context) {
|
||||
if !f.settings.GetBool(settings.CacheEnabledKey) {
|
||||
f.Println("The local cache is already disabled.")
|
||||
return
|
||||
}
|
||||
|
||||
if f.yesNoQuestion("Are you sure you want to disable the local cache") {
|
||||
if err := f.bridge.DisableCache(); err != nil {
|
||||
f.Println("The local cache could not be disabled.")
|
||||
return
|
||||
}
|
||||
|
||||
f.restarter.SetToRestart()
|
||||
f.Stop()
|
||||
}
|
||||
}
|
||||
|
||||
func (f *frontendCLI) setCacheOnDiskLocation(c *ishell.Context) {
|
||||
if !f.settings.GetBool(settings.CacheEnabledKey) {
|
||||
f.Println("The local cache must be enabled.")
|
||||
return
|
||||
}
|
||||
|
||||
if location := f.settings.Get(settings.CacheLocationKey); location != "" {
|
||||
f.Println("The current local cache location is:", location)
|
||||
}
|
||||
|
||||
if location := f.readStringInAttempts("Enter a new location for the cache", c.ReadLine, f.isCacheLocationUsable); location != "" {
|
||||
if err := f.bridge.MigrateCache(f.settings.Get(settings.CacheLocationKey), location); err != nil {
|
||||
f.Println("The local cache location could not be changed.")
|
||||
return
|
||||
}
|
||||
|
||||
f.restarter.SetToRestart()
|
||||
f.Stop()
|
||||
}
|
||||
}
|
||||
|
||||
@ -168,3 +225,13 @@ func (f *frontendCLI) isPortFree(port string) bool {
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// NOTE(GODT-1158): Check free space in location.
|
||||
func (f *frontendCLI) isCacheLocationUsable(location string) bool {
|
||||
stat, err := os.Stat(location)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return stat.IsDir()
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2021 Proton Technologies AG
|
||||
// Copyright (c) 2022 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
@ -81,13 +81,7 @@ func (f *frontendCLI) selectEarlyChannel(c *ishell.Context) {
|
||||
f.Println("Bridge is currently on the stable update channel.")
|
||||
|
||||
if f.yesNoQuestion("Are you sure you want to switch to the early-access update channel") {
|
||||
needRestart, err := f.bridge.SetUpdateChannel(updater.EarlyChannel)
|
||||
if err != nil {
|
||||
f.Println("There was a problem switching update channel.")
|
||||
}
|
||||
if needRestart {
|
||||
f.restarter.SetToRestart()
|
||||
}
|
||||
f.bridge.SetUpdateChannel(updater.EarlyChannel)
|
||||
}
|
||||
}
|
||||
|
||||
@ -101,12 +95,6 @@ func (f *frontendCLI) selectStableChannel(c *ishell.Context) {
|
||||
f.Println("Switching to the stable channel may reset all data!")
|
||||
|
||||
if f.yesNoQuestion("Are you sure you want to switch to the stable update channel") {
|
||||
needRestart, err := f.bridge.SetUpdateChannel(updater.StableChannel)
|
||||
if err != nil {
|
||||
f.Println("There was a problem switching update channel.")
|
||||
}
|
||||
if needRestart {
|
||||
f.restarter.SetToRestart()
|
||||
}
|
||||
f.bridge.SetUpdateChannel(updater.StableChannel)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2021 Proton Technologies AG
|
||||
// Copyright (c) 2022 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
|
||||
76
internal/frontend/clientconfig/config.go
Normal file
76
internal/frontend/clientconfig/config.go
Normal file
@ -0,0 +1,76 @@
|
||||
// Copyright (c) 2022 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
// ProtonMail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// ProtonMail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
// Package clientconfig provides automatic config of IMAP and SMTP.
|
||||
// For now only for Apple Mail.
|
||||
package clientconfig
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/internal/config/settings"
|
||||
"github.com/ProtonMail/proton-bridge/internal/config/useragent"
|
||||
"github.com/ProtonMail/proton-bridge/internal/frontend/types"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type AutoConfig interface {
|
||||
Name() string
|
||||
Configure(imapPort int, smtpPort int, imapSSl, smtpSSL bool, user types.User, address string) error
|
||||
}
|
||||
|
||||
var (
|
||||
available = map[string]AutoConfig{} //nolint[gochecknoglobals]
|
||||
ErrNotAvailable = errors.New("configuration not available")
|
||||
)
|
||||
|
||||
const AppleMailClient = "Apple Mail"
|
||||
|
||||
func ConfigureAppleMail(user types.User, address string, s *settings.Settings) (needRestart bool, err error) {
|
||||
return configure(AppleMailClient, user, address, s)
|
||||
}
|
||||
|
||||
func configure(configName string, user types.User, address string, s *settings.Settings) (needRestart bool, err error) {
|
||||
log := logrus.WithField("pkg", "client_config").WithField("client", configName)
|
||||
|
||||
config, ok := available[configName]
|
||||
if !ok {
|
||||
return false, ErrNotAvailable
|
||||
}
|
||||
|
||||
imapPort := s.GetInt(settings.IMAPPortKey)
|
||||
imapSSL := false
|
||||
smtpPort := s.GetInt(settings.SMTPPortKey)
|
||||
smtpSSL := s.GetBool(settings.SMTPSSLKey)
|
||||
|
||||
if address == "" {
|
||||
address = user.GetPrimaryAddress()
|
||||
}
|
||||
|
||||
if configName == AppleMailClient {
|
||||
// If configuring apple mail for Catalina or newer, users should use SSL.
|
||||
needRestart = false
|
||||
if !smtpSSL && useragent.IsCatalinaOrNewer() {
|
||||
smtpSSL = true
|
||||
s.SetBool(settings.SMTPSSLKey, true)
|
||||
log.Warn("Detected Catalina or newer with bad SMTP SSL settings, now using SSL, bridge needs to restart")
|
||||
needRestart = true
|
||||
}
|
||||
}
|
||||
|
||||
return needRestart, config.Configure(imapPort, smtpPort, imapSSL, smtpSSL, user, address)
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2021 Proton Technologies AG
|
||||
// Copyright (c) 2022 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
@ -17,7 +17,7 @@
|
||||
|
||||
// +build darwin
|
||||
|
||||
package autoconfig
|
||||
package clientconfig
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
@ -39,17 +39,15 @@ const (
|
||||
)
|
||||
|
||||
func init() { //nolint[gochecknoinit]
|
||||
available = append(available, &appleMail{})
|
||||
available[AppleMailClient] = &appleMail{}
|
||||
}
|
||||
|
||||
type appleMail struct{}
|
||||
|
||||
func (c *appleMail) Name() string {
|
||||
return "Apple Mail"
|
||||
}
|
||||
func (c *appleMail) Name() string { return AppleMailClient }
|
||||
|
||||
func (c *appleMail) Configure(imapPort, smtpPort int, imapSSL, smtpSSL bool, user types.User, addressIndex int) error {
|
||||
mc := prepareMobileConfig(imapPort, smtpPort, imapSSL, smtpSSL, user, addressIndex)
|
||||
func (c *appleMail) Configure(imapPort, smtpPort int, imapSSL, smtpSSL bool, user types.User, address string) error {
|
||||
mc := prepareMobileConfig(imapPort, smtpPort, imapSSL, smtpSSL, user, address)
|
||||
|
||||
confPath, err := saveConfigTemporarily(mc)
|
||||
if err != nil {
|
||||
@ -63,21 +61,13 @@ func (c *appleMail) Configure(imapPort, smtpPort int, imapSSL, smtpSSL bool, use
|
||||
return exec.Command("open", confPath).Run() //nolint[gosec] G204: open command is safe, mobileconfig is generated by us
|
||||
}
|
||||
|
||||
func prepareMobileConfig(imapPort, smtpPort int, imapSSL, smtpSSL bool, user types.User, addressIndex int) *mobileconfig.Config {
|
||||
var addresses string
|
||||
var displayName string
|
||||
func prepareMobileConfig(imapPort, smtpPort int, imapSSL, smtpSSL bool, user types.User, address string) *mobileconfig.Config {
|
||||
displayName := address
|
||||
addresses := address
|
||||
|
||||
if user.IsCombinedAddressMode() {
|
||||
displayName = user.GetPrimaryAddress()
|
||||
addresses = strings.Join(user.GetAddresses(), ",")
|
||||
} else {
|
||||
for idx, address := range user.GetAddresses() {
|
||||
if idx == addressIndex {
|
||||
displayName = address
|
||||
break
|
||||
}
|
||||
}
|
||||
addresses = displayName
|
||||
}
|
||||
|
||||
timestamp := strconv.FormatInt(time.Now().Unix(), 10)
|
||||
@ -109,10 +99,10 @@ func saveConfigTemporarily(mc *mobileconfig.Config) (fname string, err error) {
|
||||
}
|
||||
|
||||
// Make sure the temporary file is deleted.
|
||||
go (func() {
|
||||
go func() {
|
||||
<-time.After(10 * time.Minute)
|
||||
_ = os.RemoveAll(dir)
|
||||
})()
|
||||
}()
|
||||
|
||||
// Make sure the file is only readable for the current user.
|
||||
fname = filepath.Clean(filepath.Join(dir, "protonmail.mobileconfig"))
|
||||
@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2021 Proton Technologies AG
|
||||
// Copyright (c) 2022 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
@ -19,27 +19,17 @@
|
||||
package frontend
|
||||
|
||||
import (
|
||||
"github.com/ProtonMail/go-autostart"
|
||||
"github.com/ProtonMail/proton-bridge/internal/bridge"
|
||||
"github.com/ProtonMail/proton-bridge/internal/config/settings"
|
||||
"github.com/ProtonMail/proton-bridge/internal/config/useragent"
|
||||
"github.com/ProtonMail/proton-bridge/internal/frontend/cli"
|
||||
cliie "github.com/ProtonMail/proton-bridge/internal/frontend/cli-ie"
|
||||
"github.com/ProtonMail/proton-bridge/internal/frontend/qt"
|
||||
qtie "github.com/ProtonMail/proton-bridge/internal/frontend/qt-ie"
|
||||
"github.com/ProtonMail/proton-bridge/internal/frontend/types"
|
||||
"github.com/ProtonMail/proton-bridge/internal/importexport"
|
||||
"github.com/ProtonMail/proton-bridge/internal/locations"
|
||||
"github.com/ProtonMail/proton-bridge/internal/updater"
|
||||
"github.com/ProtonMail/proton-bridge/pkg/listener"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var (
|
||||
log = logrus.WithField("pkg", "frontend") // nolint[unused]
|
||||
)
|
||||
|
||||
// Frontend is an interface to be implemented by each frontend type (cli, gui, html).
|
||||
type Frontend interface {
|
||||
Loop() error
|
||||
NotifyManualUpdate(update updater.VersionInfo, canInstall bool)
|
||||
@ -64,58 +54,11 @@ func New(
|
||||
userAgent *useragent.UserAgent,
|
||||
bridge *bridge.Bridge,
|
||||
noEncConfirmator types.NoEncConfirmator,
|
||||
autostart *autostart.App,
|
||||
restarter types.Restarter,
|
||||
) Frontend {
|
||||
bridgeWrap := types.NewBridgeWrap(bridge)
|
||||
return newBridgeFrontend(
|
||||
version,
|
||||
buildVersion,
|
||||
programName,
|
||||
frontendType,
|
||||
showWindowOnStart,
|
||||
panicHandler,
|
||||
locations,
|
||||
settings,
|
||||
eventListener,
|
||||
updater,
|
||||
userAgent,
|
||||
bridgeWrap,
|
||||
noEncConfirmator,
|
||||
autostart,
|
||||
restarter,
|
||||
)
|
||||
}
|
||||
|
||||
func newBridgeFrontend(
|
||||
version,
|
||||
buildVersion,
|
||||
programName,
|
||||
frontendType string,
|
||||
showWindowOnStart bool,
|
||||
panicHandler types.PanicHandler,
|
||||
locations *locations.Locations,
|
||||
settings *settings.Settings,
|
||||
eventListener listener.Listener,
|
||||
updater types.Updater,
|
||||
userAgent *useragent.UserAgent,
|
||||
bridge types.Bridger,
|
||||
noEncConfirmator types.NoEncConfirmator,
|
||||
autostart *autostart.App,
|
||||
restarter types.Restarter,
|
||||
) Frontend {
|
||||
switch frontendType {
|
||||
case "cli":
|
||||
return cli.New(
|
||||
panicHandler,
|
||||
locations,
|
||||
settings,
|
||||
eventListener,
|
||||
updater,
|
||||
bridge,
|
||||
restarter,
|
||||
)
|
||||
default:
|
||||
case "qt":
|
||||
return qt.New(
|
||||
version,
|
||||
buildVersion,
|
||||
@ -127,79 +70,21 @@ func newBridgeFrontend(
|
||||
eventListener,
|
||||
updater,
|
||||
userAgent,
|
||||
bridge,
|
||||
bridgeWrap,
|
||||
noEncConfirmator,
|
||||
autostart,
|
||||
restarter,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// NewImportExport returns initialized frontend based on `frontendType`, which can be `cli` or `qt`.
|
||||
func NewImportExport(
|
||||
version,
|
||||
buildVersion,
|
||||
programName,
|
||||
frontendType string,
|
||||
panicHandler types.PanicHandler,
|
||||
locations *locations.Locations,
|
||||
settings *settings.Settings,
|
||||
eventListener listener.Listener,
|
||||
updater types.Updater,
|
||||
ie *importexport.ImportExport,
|
||||
restarter types.Restarter,
|
||||
) Frontend {
|
||||
ieWrap := types.NewImportExportWrap(ie)
|
||||
return newIEFrontend(
|
||||
version,
|
||||
buildVersion,
|
||||
programName,
|
||||
frontendType,
|
||||
case "cli":
|
||||
return cli.New(
|
||||
panicHandler,
|
||||
locations,
|
||||
settings,
|
||||
eventListener,
|
||||
updater,
|
||||
ieWrap,
|
||||
restarter,
|
||||
)
|
||||
}
|
||||
|
||||
func newIEFrontend(
|
||||
version,
|
||||
buildVersion,
|
||||
programName,
|
||||
frontendType string,
|
||||
panicHandler types.PanicHandler,
|
||||
locations *locations.Locations,
|
||||
settings *settings.Settings,
|
||||
eventListener listener.Listener,
|
||||
updater types.Updater,
|
||||
ie types.ImportExporter,
|
||||
restarter types.Restarter,
|
||||
) Frontend {
|
||||
switch frontendType {
|
||||
case "cli":
|
||||
return cliie.New(
|
||||
panicHandler,
|
||||
locations,
|
||||
eventListener,
|
||||
updater,
|
||||
ie,
|
||||
bridgeWrap,
|
||||
restarter,
|
||||
)
|
||||
default:
|
||||
return qtie.New(
|
||||
version,
|
||||
buildVersion,
|
||||
programName,
|
||||
panicHandler,
|
||||
locations,
|
||||
settings,
|
||||
eventListener,
|
||||
updater,
|
||||
ie,
|
||||
restarter,
|
||||
)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
188
internal/frontend/qml/AccountDelegate.qml
Normal file
188
internal/frontend/qml/AccountDelegate.qml
Normal file
@ -0,0 +1,188 @@
|
||||
// Copyright (c) 2022 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
// ProtonMail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// ProtonMail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import QtQuick 2.13
|
||||
import QtQuick.Layouts 1.12
|
||||
import QtQuick.Controls 2.12
|
||||
|
||||
import Proton 4.0
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property ColorScheme colorScheme
|
||||
property var user
|
||||
|
||||
property var _spacing: 12
|
||||
|
||||
property color usedSpaceColor : {
|
||||
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
|
||||
return root.colorScheme.signal_danger
|
||||
}
|
||||
property real usedFraction: root.user ? reasonableFracion(root.user.usedBytes, root.user.totalBytes) : 0
|
||||
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)
|
||||
|
||||
function reasonableFracion(used, total){
|
||||
var usedSafe = root.reasonableBytes(used)
|
||||
var totalSafe = root.reasonableBytes(total)
|
||||
if (totalSafe == 0 || usedSafe == 0) return 0
|
||||
if (totalSafe <= usedSafe) return 1
|
||||
return usedSafe / totalSafe
|
||||
}
|
||||
|
||||
function reasonableBytes(bytes){
|
||||
var safeBytes = bytes+0
|
||||
if (safeBytes != bytes) return 0
|
||||
if (safeBytes < 0) return 0
|
||||
return Math.ceil(safeBytes)
|
||||
}
|
||||
|
||||
function spaceWithUnits(bytes){
|
||||
if (bytes*1 !== bytes || bytes == 0 ) return "0 kB"
|
||||
var units = ['B',"kB", "MB", "GB", "TB"];
|
||||
var i = parseInt(Math.floor(Math.log(bytes)/Math.log(1024)));
|
||||
|
||||
return Math.round(bytes*10 / Math.pow(1024, i))/10 + " " + units[i]
|
||||
}
|
||||
|
||||
// width expected to be set by parent object
|
||||
implicitHeight : children[0].implicitHeight
|
||||
|
||||
enum ViewType{
|
||||
SmallView, LargeView
|
||||
}
|
||||
property var type : AccountDelegate.SmallView
|
||||
|
||||
RowLayout {
|
||||
spacing: root._spacing
|
||||
|
||||
anchors {
|
||||
top: root.top
|
||||
left: root.left
|
||||
right: root.rigth
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: avatar
|
||||
|
||||
Layout.fillHeight: true
|
||||
Layout.preferredWidth: height
|
||||
|
||||
radius: 4
|
||||
|
||||
color: root.colorScheme.background_avatar
|
||||
|
||||
Label {
|
||||
colorScheme: root.colorScheme
|
||||
anchors.fill: parent
|
||||
text: root.user ? root.user.avatarText.toUpperCase(): ""
|
||||
type: {
|
||||
switch (root.type) {
|
||||
case AccountDelegate.SmallView: return Label.Body
|
||||
case AccountDelegate.LargeView: return Label.Title
|
||||
}
|
||||
}
|
||||
font.weight: Font.Normal
|
||||
color: "#FFFFFF"
|
||||
horizontalAlignment: Qt.AlignHCenter
|
||||
verticalAlignment: Qt.AlignVCenter
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: account
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
|
||||
spacing: 0
|
||||
|
||||
Label {
|
||||
Layout.maximumWidth: root.width - (
|
||||
root._spacing + avatar.width
|
||||
)
|
||||
|
||||
colorScheme: root.colorScheme
|
||||
text: root.user ? user.username : ""
|
||||
type: {
|
||||
switch (root.type) {
|
||||
case AccountDelegate.SmallView: return Label.Body
|
||||
case AccountDelegate.LargeView: return Label.Title
|
||||
}
|
||||
}
|
||||
elide: Text.ElideMiddle
|
||||
}
|
||||
|
||||
Item { implicitHeight: root.type == AccountDelegate.LargeView ? 6 : 0 }
|
||||
|
||||
RowLayout {
|
||||
spacing: 0
|
||||
Label {
|
||||
colorScheme: root.colorScheme
|
||||
text: root.user && root.user.loggedIn ? root.usedSpace : qsTr("Signed out")
|
||||
color: root.usedSpaceColor
|
||||
type: {
|
||||
switch (root.type) {
|
||||
case AccountDelegate.SmallView: return Label.Caption
|
||||
case AccountDelegate.LargeView: return Label.Body
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Label {
|
||||
colorScheme: root.colorScheme
|
||||
text: root.user && root.user.loggedIn ? " / " + root.totalSpace : ""
|
||||
color: root.colorScheme.text_weak
|
||||
type: {
|
||||
switch (root.type) {
|
||||
case AccountDelegate.SmallView: return Label.Caption
|
||||
case AccountDelegate.LargeView: return Label.Body
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Rectangle {
|
||||
visible: root.user ? root.type == AccountDelegate.LargeView : false
|
||||
width: 140
|
||||
height: 4
|
||||
radius: 3
|
||||
color: root.colorScheme.border_weak
|
||||
|
||||
Rectangle {
|
||||
radius: 3
|
||||
color: root.usedSpaceColor
|
||||
visible: root.user ? parent.visible && root.user.loggedIn : false
|
||||
anchors {
|
||||
top : parent.top
|
||||
bottom : parent.bottom
|
||||
left : parent.left
|
||||
}
|
||||
width: Math.min(1,Math.max(0.02,root.usedFraction)) * parent.width
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
}
|
||||
250
internal/frontend/qml/AccountView.qml
Normal file
250
internal/frontend/qml/AccountView.qml
Normal file
@ -0,0 +1,250 @@
|
||||
// Copyright (c) 2022 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
// ProtonMail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// ProtonMail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import QtQuick 2.13
|
||||
import QtQuick.Layouts 1.12
|
||||
import QtQuick.Controls 2.12
|
||||
|
||||
import Proton 4.0
|
||||
|
||||
Item {
|
||||
id: root
|
||||
property ColorScheme colorScheme
|
||||
property var backend
|
||||
property var notifications
|
||||
property var user
|
||||
|
||||
signal showSignIn()
|
||||
signal showSetupGuide(var user, string address)
|
||||
|
||||
property int _leftMargin: 64
|
||||
property int _rightMargin: 64
|
||||
property int _topMargin: 32
|
||||
property int _detailsTopMargin: 25
|
||||
property int _bottomMargin: 12
|
||||
property int _spacing: 20
|
||||
property int _lineWidth: 1
|
||||
|
||||
ScrollView {
|
||||
id: scrollView
|
||||
clip: true
|
||||
|
||||
anchors.fill: parent
|
||||
|
||||
Item {
|
||||
// can't use parent here because parent is not ScrollView (Flickable inside contentItem inside ScrollView)
|
||||
width: scrollView.availableWidth
|
||||
height: scrollView.availableHeight
|
||||
|
||||
implicitHeight: children[0].implicitHeight + children[0].anchors.topMargin + children[0].anchors.bottomMargin
|
||||
// do not set implicitWidth because implicit width of ColumnLayout will be equal to maximum implicit width of
|
||||
// internal items. And if one of internal items would be a Text or Label - implicit width of those is always
|
||||
// equal to non-wrapped text (i.e. one line only). That will lead to enabling horizontal scroll when not needed
|
||||
implicitWidth: width
|
||||
|
||||
ColumnLayout {
|
||||
spacing: 0
|
||||
|
||||
anchors.fill: parent
|
||||
|
||||
Rectangle {
|
||||
id: topRectangle
|
||||
color: root.colorScheme.background_norm
|
||||
|
||||
implicitHeight: children[0].implicitHeight + children[0].anchors.topMargin + children[0].anchors.bottomMargin
|
||||
implicitWidth: children[0].implicitWidth + children[0].anchors.leftMargin + children[0].anchors.rightMargin
|
||||
|
||||
Layout.fillWidth: true
|
||||
|
||||
ColumnLayout {
|
||||
spacing: root._spacing
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: root._leftMargin
|
||||
anchors.rightMargin: root._rightMargin
|
||||
anchors.topMargin: root._topMargin
|
||||
anchors.bottomMargin: root._bottomMargin
|
||||
|
||||
RowLayout { // account delegate with action buttons
|
||||
Layout.fillWidth: true
|
||||
|
||||
AccountDelegate {
|
||||
Layout.fillWidth: true
|
||||
colorScheme: root.colorScheme
|
||||
user: root.user
|
||||
type: AccountDelegate.LargeView
|
||||
enabled: root.user ? root.user.loggedIn : false
|
||||
}
|
||||
|
||||
Button {
|
||||
Layout.alignment: Qt.AlignTop
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Sign out")
|
||||
secondary: true
|
||||
visible: root.user ? root.user.loggedIn : false
|
||||
onClicked: {
|
||||
if (!root.user) return
|
||||
root.user.logout()
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
Layout.alignment: Qt.AlignTop
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Sign in")
|
||||
secondary: true
|
||||
visible: root.user ? !root.user.loggedIn : false
|
||||
onClicked: {
|
||||
if (!root.user) return
|
||||
root.showSignIn()
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
Layout.alignment: Qt.AlignTop
|
||||
colorScheme: root.colorScheme
|
||||
icon.source: "icons/ic-trash.svg"
|
||||
secondary: true
|
||||
onClicked: {
|
||||
if (!root.user) return
|
||||
root.notifications.askDeleteAccount(root.user)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
height: root._lineWidth
|
||||
color: root.colorScheme.border_weak
|
||||
}
|
||||
|
||||
SettingsItem {
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Email clients")
|
||||
actionText: qsTr("Configure")
|
||||
description: qsTr("Using the mailbox details below (re)configure your client.")
|
||||
type: SettingsItem.Button
|
||||
enabled: root.user ? root.user.loggedIn : false
|
||||
visible: root.user ? !root.user.splitMode || root.user.addresses.length==1 : false
|
||||
showSeparator: splitMode.visible
|
||||
onClicked: {
|
||||
if (!root.user) return
|
||||
root.showSetupGuide(root.user, user.addresses[0])
|
||||
}
|
||||
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
SettingsItem {
|
||||
id: splitMode
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Split addresses")
|
||||
description: qsTr("Setup multiple email addresses individually.")
|
||||
type: SettingsItem.Toggle
|
||||
checked: root.user ? root.user.splitMode : false
|
||||
visible: root.user ? root.user.addresses.length > 1 : false
|
||||
enabled: root.user ? root.user.loggedIn : false
|
||||
showSeparator: addressSelector.visible
|
||||
onClicked: {
|
||||
if (!splitMode.checked){
|
||||
root.notifications.askEnableSplitMode(user)
|
||||
} else {
|
||||
root.user.toggleSplitMode(!splitMode.checked)
|
||||
}
|
||||
}
|
||||
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
enabled: root.user ? root.user.loggedIn : false
|
||||
visible: root.user ? root.user.splitMode : false
|
||||
|
||||
ComboBox {
|
||||
id: addressSelector
|
||||
colorScheme: root.colorScheme
|
||||
Layout.fillWidth: true
|
||||
model: root.user ? root.user.addresses : null
|
||||
}
|
||||
|
||||
Button {
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Configure")
|
||||
secondary: true
|
||||
onClicked: {
|
||||
if (!root.user) return
|
||||
root.showSetupGuide(root.user, addressSelector.displayText)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
color: root.colorScheme.background_weak
|
||||
|
||||
implicitHeight: children[0].implicitHeight + children[0].anchors.topMargin + children[0].anchors.bottomMargin
|
||||
implicitWidth: children[0].implicitWidth + children[0].anchors.leftMargin + children[0].anchors.rightMargin
|
||||
|
||||
Layout.fillWidth: true
|
||||
|
||||
ColumnLayout {
|
||||
id: configuration
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.leftMargin: root._leftMargin
|
||||
anchors.rightMargin: root._rightMargin
|
||||
anchors.topMargin: root._detailsTopMargin
|
||||
anchors.bottomMargin: root._spacing
|
||||
|
||||
spacing: root._spacing
|
||||
visible: root.user ? root.user.loggedIn : false
|
||||
|
||||
property string currentAddress: addressSelector.displayText
|
||||
|
||||
Label {
|
||||
colorScheme: root.colorScheme
|
||||
text: qsTr("Mailbox details")
|
||||
type: Label.Body_semibold
|
||||
}
|
||||
|
||||
Configuration {
|
||||
colorScheme: root.colorScheme
|
||||
title: qsTr("IMAP")
|
||||
hostname: root.backend.hostname
|
||||
port: root.backend.portIMAP.toString()
|
||||
username: configuration.currentAddress
|
||||
password: root.user ? root.user.password : ""
|
||||
security: "STARTTLS"
|
||||
}
|
||||
|
||||
Configuration {
|
||||
colorScheme: root.colorScheme
|
||||
title: qsTr("SMTP")
|
||||
hostname : root.backend.hostname
|
||||
port : root.backend.portSMTP.toString()
|
||||
username : configuration.currentAddress
|
||||
password : root.user ? root.user.password : ""
|
||||
security : root.backend.useSSLforSMTP ? "SSL" : "STARTTLS"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
233
internal/frontend/qml/Banner.qml
Normal file
233
internal/frontend/qml/Banner.qml
Normal file
@ -0,0 +1,233 @@
|
||||
// Copyright (c) 2022 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
// ProtonMail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// ProtonMail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import QtQuick 2.13
|
||||
import QtQuick.Layouts 1.12
|
||||
import QtQuick.Controls 2.12
|
||||
import QtQuick.Controls.impl 2.12
|
||||
|
||||
import Proton 4.0
|
||||
import Notifications 1.0
|
||||
|
||||
Popup {
|
||||
id: root
|
||||
|
||||
property ColorScheme colorScheme
|
||||
property Notification notification
|
||||
property var mainWindow
|
||||
|
||||
topMargin: 37
|
||||
leftMargin: (mainWindow.width - root.implicitWidth)/2
|
||||
|
||||
implicitHeight: contentLayout.implicitHeight + contentLayout.anchors.topMargin + contentLayout.anchors.bottomMargin
|
||||
implicitWidth: 600 // contentLayout.implicitWidth + contentLayout.anchors.leftMargin + contentLayout.anchors.rightMargin
|
||||
|
||||
popupType: ApplicationWindow.PopupType.Banner
|
||||
|
||||
shouldShow: notification ? (notification.active && !notification.dismissed) : false
|
||||
|
||||
modal: false
|
||||
|
||||
Action {
|
||||
id: defaultDismissAction
|
||||
|
||||
text: qsTr("OK")
|
||||
onTriggered: {
|
||||
if (!root.notification) {
|
||||
return
|
||||
}
|
||||
|
||||
root.notification.dismissed = true
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: contentLayout
|
||||
anchors.fill: parent
|
||||
spacing: 0
|
||||
|
||||
Item {
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
|
||||
clip: true
|
||||
implicitHeight: children[1].implicitHeight + children[1].anchors.topMargin + children[1].anchors.bottomMargin
|
||||
implicitWidth: children[1].implicitWidth + children[1].anchors.leftMargin + children[1].anchors.rightMargin
|
||||
|
||||
Rectangle {
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
width: parent.width + 10
|
||||
radius: 10
|
||||
color: {
|
||||
if (!root.notification) {
|
||||
return "transparent"
|
||||
}
|
||||
|
||||
switch (root.notification.type) {
|
||||
case Notification.NotificationType.Info:
|
||||
return root.colorScheme.signal_info
|
||||
case Notification.NotificationType.Success:
|
||||
return root.colorScheme.signal_success
|
||||
case Notification.NotificationType.Warning:
|
||||
return root.colorScheme.signal_warning
|
||||
case Notification.NotificationType.Danger:
|
||||
return root.colorScheme.signal_danger
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
|
||||
anchors.topMargin: 14
|
||||
anchors.bottomMargin: 14
|
||||
anchors.leftMargin: 16
|
||||
|
||||
spacing: 8
|
||||
|
||||
ColorImage {
|
||||
color: root.colorScheme.text_invert
|
||||
width: 24
|
||||
height: 24
|
||||
|
||||
sourceSize.width: 24
|
||||
sourceSize.height: 24
|
||||
|
||||
Layout.preferredHeight: 24
|
||||
Layout.preferredWidth: 24
|
||||
|
||||
source: {
|
||||
if (!root.notification) {
|
||||
return ""
|
||||
}
|
||||
|
||||
switch (root.notification.type) {
|
||||
case Notification.NotificationType.Info:
|
||||
return "./icons/ic-info-circle-filled.svg"
|
||||
case Notification.NotificationType.Success:
|
||||
return "./icons/ic-info-circle-filled.svg"
|
||||
case Notification.NotificationType.Warning:
|
||||
return "./icons/ic-exclamation-circle-filled.svg"
|
||||
case Notification.NotificationType.Danger:
|
||||
return "./icons/ic-exclamation-circle-filled.svg"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Label {
|
||||
colorScheme: root.colorScheme
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
|
||||
color: root.colorScheme.text_invert
|
||||
text: root.notification ? root.notification.description : ""
|
||||
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillHeight: true
|
||||
width: 1
|
||||
color: {
|
||||
if (!root.notification) {
|
||||
return "transparent"
|
||||
}
|
||||
|
||||
switch (root.notification.type) {
|
||||
case Notification.NotificationType.Info:
|
||||
return root.colorScheme.signal_info_active
|
||||
case Notification.NotificationType.Success:
|
||||
return root.colorScheme.signal_success_active
|
||||
case Notification.NotificationType.Warning:
|
||||
return root.colorScheme.signal_warning_active
|
||||
case Notification.NotificationType.Danger:
|
||||
return root.colorScheme.signal_danger_active
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
colorScheme: root.colorScheme
|
||||
Layout.fillHeight: true
|
||||
|
||||
id: actionButton
|
||||
|
||||
action: (root.notification && root.notification.action.length > 0) ? root.notification.action[0] : defaultDismissAction
|
||||
|
||||
background: Item {
|
||||
clip: true
|
||||
Rectangle {
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.right: parent.right
|
||||
width: parent.width + 10
|
||||
radius: 10
|
||||
color: {
|
||||
if (!root.notification) {
|
||||
return "transparent"
|
||||
}
|
||||
|
||||
var norm
|
||||
var hover
|
||||
var active
|
||||
|
||||
switch (root.notification.type) {
|
||||
case Notification.NotificationType.Info:
|
||||
norm = root.colorScheme.signal_info
|
||||
hover = root.colorScheme.signal_info_hover
|
||||
active = root.colorScheme.signal_info_active
|
||||
break;
|
||||
case Notification.NotificationType.Success:
|
||||
norm = root.colorScheme.signal_success
|
||||
hover = root.colorScheme.signal_success_hover
|
||||
active = root.colorScheme.signal_success_active
|
||||
break;
|
||||
case Notification.NotificationType.Warning:
|
||||
norm = root.colorScheme.signal_warning
|
||||
hover = root.colorScheme.signal_warning_hover
|
||||
active = root.colorScheme.signal_warning_active
|
||||
break;
|
||||
case Notification.NotificationType.Danger:
|
||||
norm = root.colorScheme.signal_danger
|
||||
hover = root.colorScheme.signal_danger_hover
|
||||
active = root.colorScheme.signal_danger_active
|
||||
break;
|
||||
}
|
||||
|
||||
if (actionButton.down) {
|
||||
return active
|
||||
}
|
||||
|
||||
if (actionButton.enabled && (actionButton.highlighted || actionButton.hovered || actionButton.checked)) {
|
||||
return hover
|
||||
}
|
||||
|
||||
if (actionButton.loading) {
|
||||
return hover
|
||||
}
|
||||
|
||||
return norm
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
266
internal/frontend/qml/Bridge.qml
Normal file
266
internal/frontend/qml/Bridge.qml
Normal file
@ -0,0 +1,266 @@
|
||||
// Copyright (c) 2022 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
// ProtonMail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// ProtonMail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import QtQml 2.12
|
||||
import QtQuick 2.13
|
||||
import QtQuick.Window 2.13
|
||||
import Qt.labs.platform 1.1
|
||||
|
||||
import Proton 4.0
|
||||
import Notifications 1.0
|
||||
|
||||
QtObject {
|
||||
id: root
|
||||
|
||||
function isInInterval(num, lower_limit, upper_limit) {
|
||||
return lower_limit <= num && num <= upper_limit
|
||||
}
|
||||
function bound(num, lower_limit, upper_limit) {
|
||||
return Math.max(lower_limit, Math.min(upper_limit, num))
|
||||
}
|
||||
|
||||
property var backend
|
||||
|
||||
property Notifications _notifications: Notifications {
|
||||
id: notifications
|
||||
backend: root.backend
|
||||
frontendMain: mainWindow
|
||||
frontendStatus: statusWindow
|
||||
frontendTray: trayIcon
|
||||
}
|
||||
|
||||
property MainWindow _mainWindow: MainWindow {
|
||||
id: mainWindow
|
||||
visible: false
|
||||
|
||||
backend: root.backend
|
||||
notifications: root._notifications
|
||||
|
||||
onVisibleChanged: {
|
||||
backend.dockIconVisible = visible
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: root.backend
|
||||
onCacheUnavailable: {
|
||||
mainWindow.showAndRise()
|
||||
}
|
||||
onColorSchemeNameChanged: root.setColorScheme()
|
||||
}
|
||||
}
|
||||
|
||||
property StatusWindow _statusWindow: StatusWindow {
|
||||
id: statusWindow
|
||||
visible: false
|
||||
|
||||
backend: root.backend
|
||||
notifications: root._notifications
|
||||
|
||||
onShowMainWindow: {
|
||||
mainWindow.showAndRise()
|
||||
}
|
||||
|
||||
onShowHelp: {
|
||||
mainWindow.showHelp()
|
||||
mainWindow.showAndRise()
|
||||
}
|
||||
|
||||
onShowSettings: {
|
||||
mainWindow.showSettings()
|
||||
mainWindow.showAndRise()
|
||||
}
|
||||
|
||||
onShowSignIn: {
|
||||
mainWindow.showSignIn(username)
|
||||
mainWindow.showAndRise()
|
||||
}
|
||||
|
||||
onQuit: {
|
||||
backend.quit()
|
||||
}
|
||||
|
||||
property rect screenRect
|
||||
property rect iconRect
|
||||
|
||||
// use binding from function with width and height as arguments so it will be recalculated every time width and height are changed
|
||||
property point position: getPosition(width, height)
|
||||
x: position.x
|
||||
y: position.y
|
||||
|
||||
function getPosition(_width, _height) {
|
||||
if (screenRect.width === 0 || screenRect.height === 0) {
|
||||
return Qt.point(0, 0)
|
||||
}
|
||||
|
||||
var _x = 0
|
||||
var _y = 0
|
||||
|
||||
// fit above
|
||||
_y = iconRect.top - height
|
||||
if (isInInterval(_y, screenRect.top, screenRect.bottom - height)) {
|
||||
// position preferebly in the horizontal center but bound to the screen rect
|
||||
_x = bound(iconRect.left + (iconRect.width - width)/2, screenRect.left, screenRect.right - width)
|
||||
return Qt.point(_x, _y)
|
||||
}
|
||||
|
||||
// fit below
|
||||
_y = iconRect.bottom
|
||||
if (isInInterval(_y, screenRect.top, screenRect.bottom - height)) {
|
||||
// position preferebly in the horizontal center but bound to the screen rect
|
||||
_x = bound(iconRect.left + (iconRect.width - width)/2, screenRect.left, screenRect.right - width)
|
||||
return Qt.point(_x, _y)
|
||||
}
|
||||
|
||||
// fit to the left
|
||||
_x = iconRect.left - width
|
||||
if (isInInterval(_x, screenRect.left, screenRect.right - width)) {
|
||||
// position preferebly in the vertical center but bound to the screen rect
|
||||
_y = bound(iconRect.top + (iconRect.height - height)/2, screenRect.top, screenRect.bottom - height)
|
||||
return Qt.point(_x, _y)
|
||||
}
|
||||
|
||||
// fir to the right
|
||||
_x = iconRect.right
|
||||
if (isInInterval(_x, screenRect.left, screenRect.right - width)) {
|
||||
// position preferebly in the vertical center but bound to the screen rect
|
||||
_y = bound(iconRect.top + (iconRect.height - height)/2, screenRect.top, screenRect.bottom - height)
|
||||
return Qt.point(_x, _y)
|
||||
}
|
||||
|
||||
// Fallback: position satatus window right above icon and let window manager decide.
|
||||
console.warn("Can't position status window: screenRect =", screenRect, "iconRect =", iconRect)
|
||||
_x = bound(iconRect.left + (iconRect.width - width)/2, screenRect.left, screenRect.right - width)
|
||||
_y = bound(iconRect.top + (iconRect.height - height)/2, screenRect.top, screenRect.bottom - height)
|
||||
return Qt.point(_x, _y)
|
||||
}
|
||||
}
|
||||
|
||||
property SystemTrayIcon _trayIcon: SystemTrayIcon {
|
||||
id: trayIcon
|
||||
visible: true
|
||||
icon.source: "./icons/systray-mono.png"
|
||||
icon.mask: true // make sure that systems like macOS will use proper color
|
||||
tooltip: `Proton Mail Bridge v${backend.version}`
|
||||
onActivated: {
|
||||
function calcStatusWindowPosition() {
|
||||
// On some platforms (X11 / Plasma) Qt does not provide icon position and geometry info.
|
||||
// In this case we rely on cursor position
|
||||
var iconRect = Qt.rect(geometry.x, geometry.y, geometry.width, geometry.height)
|
||||
if (geometry.width == 0 && geometry.height == 0) {
|
||||
var mousePos = backend.getCursorPos()
|
||||
iconRect.x = mousePos.x
|
||||
iconRect.y = mousePos.y
|
||||
iconRect.width = 0
|
||||
iconRect.height = 0
|
||||
}
|
||||
|
||||
// Find screen
|
||||
var screen
|
||||
for (var i in Qt.application.screens) {
|
||||
var _screen = Qt.application.screens[i]
|
||||
if (
|
||||
isInInterval(iconRect.x, _screen.virtualX, _screen.virtualX + _screen.width) &&
|
||||
isInInterval(iconRect.y, _screen.virtualY, _screen.virtualY + _screen.height)
|
||||
) {
|
||||
screen = _screen
|
||||
break
|
||||
}
|
||||
}
|
||||
if (!screen) {
|
||||
// Fallback to primary screen
|
||||
screen = Qt.application.screens[0]
|
||||
}
|
||||
|
||||
// In case we used mouse to detect icon position - we want to make a fake icon rectangle from a point
|
||||
if (iconRect.width == 0 && iconRect.height == 0) {
|
||||
iconRect.x = bound(iconRect.x - 16, screen.virtualX, screen.virtualX + screen.width - 32)
|
||||
iconRect.y = bound(iconRect.y - 16, screen.virtualY, screen.virtualY + screen.height - 32)
|
||||
iconRect.width = 32
|
||||
iconRect.height = 32
|
||||
}
|
||||
|
||||
statusWindow.screenRect = Qt.rect(screen.virtualX, screen.virtualY, screen.width, screen.height)
|
||||
statusWindow.iconRect = iconRect
|
||||
}
|
||||
|
||||
function toggleWindow(win) {
|
||||
if (win.visible) {
|
||||
win.close()
|
||||
} else {
|
||||
win.showAndRise()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
switch (reason) {
|
||||
case SystemTrayIcon.Unknown:
|
||||
break;
|
||||
case SystemTrayIcon.Context:
|
||||
case SystemTrayIcon.Trigger:
|
||||
case SystemTrayIcon.DoubleClick:
|
||||
case SystemTrayIcon.MiddleClick:
|
||||
calcStatusWindowPosition()
|
||||
toggleWindow(statusWindow)
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
if (!root.backend) {
|
||||
console.log("backend not loaded")
|
||||
}
|
||||
|
||||
root.setColorScheme()
|
||||
|
||||
|
||||
if (!root.backend.users) {
|
||||
console.log("users not loaded")
|
||||
}
|
||||
|
||||
var c = root.backend.users.count
|
||||
var u = root.backend.users.get(0)
|
||||
// DEBUG
|
||||
if (c != 0) {
|
||||
console.log("users non zero", c)
|
||||
console.log("first user", u )
|
||||
}
|
||||
|
||||
if (c === 0) {
|
||||
mainWindow.showAndRise()
|
||||
}
|
||||
|
||||
if (u) {
|
||||
if (c === 1 && u.loggedIn === false) {
|
||||
mainWindow.showAndRise()
|
||||
}
|
||||
}
|
||||
|
||||
if (root.backend.showOnStartup) {
|
||||
mainWindow.showAndRise()
|
||||
}
|
||||
|
||||
root.backend.guiReady()
|
||||
}
|
||||
|
||||
function setColorScheme() {
|
||||
if (root.backend.colorSchemeName == "light") ProtonStyle.currentStyle = ProtonStyle.lightStyle
|
||||
if (root.backend.colorSchemeName == "dark") ProtonStyle.currentStyle = ProtonStyle.darkStyle
|
||||
}
|
||||
}
|
||||
316
internal/frontend/qml/BridgeTest/UserControl.qml
Normal file
316
internal/frontend/qml/BridgeTest/UserControl.qml
Normal file
@ -0,0 +1,316 @@
|
||||
// Copyright (c) 2022 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
// ProtonMail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// ProtonMail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import QtQml 2.12
|
||||
import QtQuick 2.13
|
||||
import QtQuick.Layouts 1.12
|
||||
import QtQuick.Controls 2.13
|
||||
|
||||
import Proton 4.0
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
|
||||
property var user
|
||||
property var userIndex
|
||||
property var backend
|
||||
|
||||
spacing : 5
|
||||
|
||||
Layout.fillHeight: true
|
||||
//Layout.fillWidth: true
|
||||
|
||||
property ColorScheme colorScheme
|
||||
|
||||
TextField {
|
||||
colorScheme: root.colorScheme
|
||||
Layout.fillWidth: true
|
||||
|
||||
text: user !== undefined ? user.username : ""
|
||||
|
||||
onEditingFinished: {
|
||||
user.username = text
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
Switch {
|
||||
id: userLoginSwitch
|
||||
colorScheme: root.colorScheme
|
||||
|
||||
text: "LoggedIn"
|
||||
enabled: user !== undefined && user.username.length > 0
|
||||
|
||||
checked: user ? user.loggedIn : false
|
||||
|
||||
onCheckedChanged: {
|
||||
if (!user) {
|
||||
return
|
||||
}
|
||||
|
||||
if (checked) {
|
||||
if (user === backend.loginUser) {
|
||||
var newUserObject = backend.userComponent.createObject(backend, {username: user.username, loggedIn: true, setupGuideSeen: user.setupGuideSeen})
|
||||
backend.users.append( { object: newUserObject } )
|
||||
|
||||
user.username = ""
|
||||
user.resetLoginRequests()
|
||||
return
|
||||
}
|
||||
|
||||
user.loggedIn = true
|
||||
user.resetLoginRequests()
|
||||
return
|
||||
} else {
|
||||
user.loggedIn = false
|
||||
user.resetLoginRequests()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Switch {
|
||||
colorScheme: root.colorScheme
|
||||
|
||||
text: "Setup guide seen"
|
||||
enabled: user !== undefined && user.username.length > 0
|
||||
|
||||
checked: user ? user.setupGuideSeen : false
|
||||
|
||||
onCheckedChanged: {
|
||||
if (!user) {
|
||||
return
|
||||
}
|
||||
|
||||
user.setupGuideSeen = checked
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
Label {
|
||||
colorScheme: root.colorScheme
|
||||
id: loginLabel
|
||||
text: "Login:"
|
||||
|
||||
Layout.preferredWidth: Math.max(loginLabel.implicitWidth, faLabel.implicitWidth, passLabel.implicitWidth)
|
||||
}
|
||||
|
||||
Button {
|
||||
colorScheme: root.colorScheme
|
||||
text: "name/pass error"
|
||||
enabled: user !== undefined //&& user.isLoginRequested && !user.isLogin2FARequested && !user.isLogin2PasswordProvided
|
||||
|
||||
onClicked: {
|
||||
root.backend.loginUsernamePasswordError("")
|
||||
user.resetLoginRequests()
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
colorScheme: root.colorScheme
|
||||
text: "free user error"
|
||||
enabled: user !== undefined //&& user.isLoginRequested
|
||||
onClicked: {
|
||||
root.backend.loginFreeUserError()
|
||||
user.resetLoginRequests()
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
colorScheme: root.colorScheme
|
||||
text: "connection error"
|
||||
enabled: user !== undefined //&& user.isLoginRequested
|
||||
onClicked: {
|
||||
root.backend.loginConnectionError("")
|
||||
user.resetLoginRequests()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
Label {
|
||||
colorScheme: root.colorScheme
|
||||
id: faLabel
|
||||
text: "2FA:"
|
||||
|
||||
Layout.preferredWidth: Math.max(loginLabel.implicitWidth, faLabel.implicitWidth, passLabel.implicitWidth)
|
||||
}
|
||||
|
||||
Button {
|
||||
colorScheme: root.colorScheme
|
||||
text: "request"
|
||||
|
||||
enabled: user !== undefined //&& user.isLoginRequested && !user.isLogin2FARequested && !user.isLogin2PasswordRequested
|
||||
onClicked: {
|
||||
root.backend.login2FARequested(user.username)
|
||||
user.isLogin2FARequested = true
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
colorScheme: root.colorScheme
|
||||
text: "error"
|
||||
|
||||
enabled: user !== undefined //&& user.isLogin2FAProvided && !(user.isLogin2PasswordRequested && !user.isLogin2PasswordProvided)
|
||||
onClicked: {
|
||||
root.backend.login2FAError("")
|
||||
user.isLogin2FAProvided = false
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
colorScheme: root.colorScheme
|
||||
text: "Abort"
|
||||
|
||||
enabled: user !== undefined //&& user.isLogin2FAProvided && !(user.isLogin2PasswordRequested && !user.isLogin2PasswordProvided)
|
||||
onClicked: {
|
||||
root.backend.login2FAErrorAbort("")
|
||||
user.resetLoginRequests()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
Label {
|
||||
colorScheme: root.colorScheme
|
||||
id: passLabel
|
||||
text: "2 Password:"
|
||||
|
||||
Layout.preferredWidth: Math.max(loginLabel.implicitWidth, faLabel.implicitWidth, passLabel.implicitWidth)
|
||||
}
|
||||
|
||||
Button {
|
||||
colorScheme: root.colorScheme
|
||||
text: "request"
|
||||
|
||||
enabled: user !== undefined //&& user.isLoginRequested && !user.isLogin2PasswordRequested && !(user.isLogin2FARequested && !user.isLogin2FAProvided)
|
||||
onClicked: {
|
||||
root.backend.login2PasswordRequested("")
|
||||
user.isLogin2PasswordRequested = true
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
colorScheme: root.colorScheme
|
||||
text: "error"
|
||||
|
||||
enabled: user !== undefined //&& user.isLogin2PasswordProvided && !(user.isLogin2FARequested && !user.isLogin2FAProvided)
|
||||
onClicked: {
|
||||
root.backend.login2PasswordError("")
|
||||
|
||||
user.isLogin2PasswordProvided = false
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
colorScheme: root.colorScheme
|
||||
text: "Abort"
|
||||
|
||||
enabled: user !== undefined //&& user.isLogin2PasswordProvided && !(user.isLogin2FARequested && !user.isLogin2FAProvided)
|
||||
onClicked: {
|
||||
root.backend.login2PasswordErrorAbort("")
|
||||
user.resetLoginRequests()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Button {
|
||||
colorScheme: root.colorScheme
|
||||
text: "Login Finished"
|
||||
|
||||
onClicked: {
|
||||
root.backend.loginFinished(0+loginFinishedIndex.text)
|
||||
user.resetLoginRequests()
|
||||
}
|
||||
}
|
||||
TextField {
|
||||
id: loginFinishedIndex
|
||||
colorScheme: root.colorScheme
|
||||
label: "Index:"
|
||||
text: root.userIndex
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Button {
|
||||
colorScheme: root.colorScheme
|
||||
text: "Already logged in"
|
||||
|
||||
onClicked: {
|
||||
root.backend.loginAlreadyLoggedIn(0+loginAlreadyLoggedInIndex.text)
|
||||
user.resetLoginRequests()
|
||||
}
|
||||
}
|
||||
TextField {
|
||||
id: loginAlreadyLoggedInIndex
|
||||
colorScheme: root.colorScheme
|
||||
label: "Index:"
|
||||
text: root.userIndex
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
TextField {
|
||||
colorScheme: root.colorScheme
|
||||
label: "used:"
|
||||
text: user && user.usedBytes ? user.usedBytes : 0
|
||||
onEditingFinished: {
|
||||
user.usedBytes = parseFloat(text)
|
||||
}
|
||||
implicitWidth: 200
|
||||
}
|
||||
TextField {
|
||||
colorScheme: root.colorScheme
|
||||
label: "total:"
|
||||
text: user && user.totalBytes ? user.totalBytes : 0
|
||||
onEditingFinished: {
|
||||
user.totalBytes = parseFloat(text)
|
||||
}
|
||||
implicitWidth: 200
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Label {colorScheme: root.colorScheme; text: "Split mode"}
|
||||
Toggle { colorScheme: root.colorScheme; checked: user ? user.splitMode : false; onClicked: {user.splitMode = !user.splitMode}}
|
||||
Button { colorScheme: root.colorScheme; text: "Toggle Finished"; onClicked: {user.toggleSplitModeFinished()}}
|
||||
}
|
||||
|
||||
TextArea { // TODO: this is causing binding loop on imlicitWidth
|
||||
colorScheme: root.colorScheme
|
||||
text: user && user.addresses ? user.addresses.join("\n") : "user@protonmail.com"
|
||||
Layout.fillWidth: true
|
||||
|
||||
onEditingFinished: {
|
||||
user.addresses = text.split("\n")
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillHeight: true
|
||||
}
|
||||
}
|
||||
102
internal/frontend/qml/BridgeTest/UserList.qml
Normal file
102
internal/frontend/qml/BridgeTest/UserList.qml
Normal file
@ -0,0 +1,102 @@
|
||||
// Copyright (c) 2022 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
// ProtonMail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// ProtonMail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import QtQuick 2.13
|
||||
import QtQuick.Layouts 1.12
|
||||
import QtQuick.Controls 2.13
|
||||
|
||||
import Proton 4.0
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
|
||||
property ColorScheme colorScheme
|
||||
property var backend
|
||||
|
||||
property alias currentIndex: usersListView.currentIndex
|
||||
ListView {
|
||||
id: usersListView
|
||||
Layout.fillHeight: true
|
||||
Layout.preferredWidth: 200
|
||||
|
||||
model: backend.usersTest
|
||||
highlightFollowsCurrentItem: true
|
||||
|
||||
delegate: Item {
|
||||
|
||||
implicitHeight: children[0].implicitHeight + anchors.topMargin + anchors.bottomMargin
|
||||
implicitWidth: children[0].implicitWidth + anchors.leftMargin + anchors.rightMargin
|
||||
|
||||
width: usersListView.width
|
||||
|
||||
anchors.margins: 10
|
||||
|
||||
Label {
|
||||
colorScheme: root.colorScheme
|
||||
text: modelData.username
|
||||
anchors.margins: 10
|
||||
anchors.fill: parent
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
usersListView.currentIndex = index
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
highlight: Rectangle {
|
||||
color: root.colorScheme.interaction_default_active
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
Button {
|
||||
colorScheme: root.colorScheme
|
||||
|
||||
text: "+"
|
||||
|
||||
onClicked: {
|
||||
var newUserObject = backend.userComponent.createObject(backend)
|
||||
newUserObject.username = backend.loginUser.username.length > 0 ? backend.loginUser.username : "test@protonmail.com"
|
||||
newUserObject.loggedIn = true
|
||||
newUserObject.setupGuideSeen = true // backend.loginUser.setupGuideSeen
|
||||
|
||||
backend.loginUser.username = ""
|
||||
backend.loginUser.loggedIn = false
|
||||
backend.loginUser.setupGuideSeen = false
|
||||
|
||||
backend.users.append( { object: newUserObject } )
|
||||
}
|
||||
}
|
||||
Button {
|
||||
colorScheme: root.colorScheme
|
||||
text: "-"
|
||||
|
||||
enabled: usersListView.currentIndex != 0
|
||||
|
||||
onClicked: {
|
||||
// var userObject = backend.users.get(usersListView.currentIndex - 1)
|
||||
backend.users.remove(usersListView.currentIndex - 1)
|
||||
// userObject.deleteLater()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2021 Proton Technologies AG
|
||||
// Copyright (c) 2022 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
@ -15,19 +15,14 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
// +build build_qt
|
||||
import QtQml.Models 2.12
|
||||
|
||||
package qt
|
||||
|
||||
const (
|
||||
TabAccount = 0
|
||||
TabSettings = 1
|
||||
TabHelp = 2
|
||||
TabQuit = 4
|
||||
TabUpdates = 100
|
||||
TabAddAccount = -1
|
||||
)
|
||||
|
||||
func (s *FrontendQt) SendNotification(tabIndex int, msg string) {
|
||||
s.Qml.NotifyBubble(tabIndex, msg)
|
||||
ListModel {
|
||||
// overriding get method to ignore any role and return directly object itself
|
||||
function get(row) {
|
||||
if (row < 0 || row >= count) {
|
||||
return undefined
|
||||
}
|
||||
return data(index(row, 0), Qt.DisplayRole)
|
||||
}
|
||||
}
|
||||
@ -1,430 +0,0 @@
|
||||
// Copyright (c) 2021 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
// ProtonMail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// ProtonMail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import QtQuick 2.8
|
||||
import ProtonUI 1.0
|
||||
import BridgeUI 1.0
|
||||
|
||||
// NOTE: Keep the Column so the height and width is inherited from content
|
||||
Column {
|
||||
id: root
|
||||
state: status
|
||||
anchors.left: parent.left
|
||||
|
||||
property int row_width: 50 * Style.px
|
||||
property int row_height: Style.accounts.heightAccount
|
||||
property var listalias : aliases.split(";")
|
||||
property int iAccount: index
|
||||
|
||||
Accessible.role: go.goos=="windows" ? Accessible.Grouping : Accessible.Row
|
||||
Accessible.name: qsTr("Account %1, status %2", "Accessible text describing account row with arguments: account name and status (connected/disconnected), resp.").arg(account).arg(statusMark.text)
|
||||
Accessible.description: Accessible.name
|
||||
Accessible.ignored: !enabled || !visible
|
||||
|
||||
// Main row
|
||||
Rectangle {
|
||||
id: mainaccRow
|
||||
anchors.left: parent.left
|
||||
width : row_width
|
||||
height : row_height
|
||||
state: { return isExpanded ? "expanded" : "collapsed" }
|
||||
color: Style.main.background
|
||||
|
||||
property string actionName : (
|
||||
isExpanded ?
|
||||
qsTr("Collapse row for account %2", "Accessible text of button showing additional configuration of account") :
|
||||
qsTr("Expand row for account %2", "Accessible text of button hiding additional configuration of account")
|
||||
). arg(account)
|
||||
|
||||
|
||||
// override by other buttons
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
anchors.fill: parent
|
||||
onClicked : {
|
||||
if (root.state=="connected") {
|
||||
mainaccRow.toggle_accountSettings()
|
||||
}
|
||||
}
|
||||
cursorShape : root.state == "connected" ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
hoverEnabled: true
|
||||
onEntered: {
|
||||
if (mainaccRow.state=="collapsed") {
|
||||
mainaccRow.color = Qt.lighter(Style.main.background,1.1)
|
||||
}
|
||||
}
|
||||
onExited: {
|
||||
if (mainaccRow.state=="collapsed") {
|
||||
mainaccRow.color = Style.main.background
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// toggle down/up icon
|
||||
Text {
|
||||
id: toggleIcon
|
||||
anchors {
|
||||
left : parent.left
|
||||
verticalCenter : parent.verticalCenter
|
||||
leftMargin : Style.main.leftMargin
|
||||
}
|
||||
color: Style.main.text
|
||||
font {
|
||||
pointSize : Style.accounts.sizeChevron * Style.pt
|
||||
family : Style.fontawesome.name
|
||||
}
|
||||
text: Style.fa.chevron_down
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
Accessible.role: Accessible.Button
|
||||
Accessible.name: mainaccRow.actionName
|
||||
Accessible.description: mainaccRow.actionName
|
||||
Accessible.onPressAction : mainaccRow.toggle_accountSettings()
|
||||
Accessible.ignored: root.state!="connected" || !root.enabled
|
||||
}
|
||||
}
|
||||
|
||||
// account name
|
||||
TextMetrics {
|
||||
id: accountMetrics
|
||||
font : accountName.font
|
||||
elide: Qt.ElideMiddle
|
||||
elideWidth: Style.accounts.elideWidth
|
||||
text: account
|
||||
}
|
||||
Text {
|
||||
id: accountName
|
||||
anchors {
|
||||
verticalCenter : parent.verticalCenter
|
||||
left : toggleIcon.left
|
||||
leftMargin : Style.main.leftMargin
|
||||
}
|
||||
color: Style.main.text
|
||||
font {
|
||||
pointSize : (Style.main.fontSize+2*Style.px) * Style.pt
|
||||
}
|
||||
text: accountMetrics.elidedText
|
||||
}
|
||||
|
||||
// status
|
||||
ClickIconText {
|
||||
id: statusMark
|
||||
anchors {
|
||||
verticalCenter : parent.verticalCenter
|
||||
left : parent.left
|
||||
leftMargin : Style.accounts.leftMargin2
|
||||
}
|
||||
text : qsTr("connected", "status of a listed logged-in account")
|
||||
iconText : Style.fa.circle_o
|
||||
textColor : Style.main.textGreen
|
||||
enabled : false
|
||||
Accessible.ignored: true
|
||||
}
|
||||
|
||||
// logout
|
||||
ClickIconText {
|
||||
id: logoutAccount
|
||||
anchors {
|
||||
verticalCenter : parent.verticalCenter
|
||||
left : parent.left
|
||||
leftMargin : Style.accounts.leftMargin3
|
||||
}
|
||||
text : qsTr("Log out", "action to log out a connected account")
|
||||
iconText : Style.fa.power_off
|
||||
textBold : true
|
||||
textColor : Style.main.textBlue
|
||||
}
|
||||
|
||||
// remove
|
||||
ClickIconText {
|
||||
id: deleteAccount
|
||||
anchors {
|
||||
verticalCenter : parent.verticalCenter
|
||||
right : parent.right
|
||||
rightMargin : Style.main.rightMargin
|
||||
}
|
||||
text : qsTr("Remove", "deletes an account from the account settings page")
|
||||
iconText : Style.fa.trash_o
|
||||
textColor : Style.main.text
|
||||
onClicked : {
|
||||
dialogGlobal.input=root.iAccount
|
||||
dialogGlobal.state="deleteUser"
|
||||
dialogGlobal.show()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// functions
|
||||
function toggle_accountSettings() {
|
||||
if (root.state=="connected") {
|
||||
if (mainaccRow.state=="collapsed" ) {
|
||||
mainaccRow.state="expanded"
|
||||
} else {
|
||||
mainaccRow.state="collapsed"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
states: [
|
||||
State {
|
||||
name: "collapsed"
|
||||
PropertyChanges { target : toggleIcon ; text : root.state=="connected" ? Style.fa.chevron_down : " " }
|
||||
PropertyChanges { target : accountName ; font.bold : false }
|
||||
PropertyChanges { target : mainaccRow ; color : Style.main.background }
|
||||
PropertyChanges { target : addressList ; visible : false }
|
||||
},
|
||||
State {
|
||||
name: "expanded"
|
||||
PropertyChanges { target : toggleIcon ; text : Style.fa.chevron_up }
|
||||
PropertyChanges { target : accountName ; font.bold : true }
|
||||
PropertyChanges { target : mainaccRow ; color : Style.accounts.backgroundExpanded }
|
||||
PropertyChanges { target : addressList ; visible : true }
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
// List of adresses
|
||||
Column {
|
||||
id: addressList
|
||||
anchors.left : parent.left
|
||||
width: row_width
|
||||
visible: false
|
||||
property alias model : repeaterAddresses.model
|
||||
|
||||
Rectangle {
|
||||
id: addressModeWrapper
|
||||
anchors {
|
||||
left : parent.left
|
||||
right : parent.right
|
||||
}
|
||||
visible : mainaccRow.state=="expanded"
|
||||
height : 2*Style.accounts.heightAddrRow/3
|
||||
color : Style.accounts.backgroundExpanded
|
||||
|
||||
ClickIconText {
|
||||
id: addressModeSwitch
|
||||
anchors {
|
||||
top : addressModeWrapper.top
|
||||
right : addressModeWrapper.right
|
||||
rightMargin : Style.main.rightMargin
|
||||
}
|
||||
textColor : Style.main.textBlue
|
||||
iconText : Style.fa.exchange
|
||||
iconOnRight : false
|
||||
text : isCombinedAddressMode ?
|
||||
qsTr("Switch to split addresses mode", "Text of button switching to mode with one configuration per each address.") :
|
||||
qsTr("Switch to combined addresses mode", "Text of button switching to mode with one configuration for all addresses.")
|
||||
|
||||
onClicked: {
|
||||
dialogGlobal.input=root.iAccount
|
||||
dialogGlobal.state="addressmode"
|
||||
dialogGlobal.show()
|
||||
}
|
||||
}
|
||||
|
||||
ClickIconText {
|
||||
id: combinedAddressConfig
|
||||
anchors {
|
||||
top : addressModeWrapper.top
|
||||
left : addressModeWrapper.left
|
||||
leftMargin : Style.accounts.leftMarginAddr+Style.main.leftMargin
|
||||
}
|
||||
visible : isCombinedAddressMode
|
||||
text : qsTr("Mailbox configuration", "Displays IMAP/SMTP settings information for a given account")
|
||||
iconText : Style.fa.gear
|
||||
textColor : Style.main.textBlue
|
||||
onClicked : {
|
||||
infoWin.showInfo(root.iAccount,0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Repeater {
|
||||
id: repeaterAddresses
|
||||
model: ["one", "two"]
|
||||
|
||||
Rectangle {
|
||||
id: addressRow
|
||||
visible: !isCombinedAddressMode
|
||||
anchors {
|
||||
left : parent.left
|
||||
right : parent.right
|
||||
}
|
||||
height: Style.accounts.heightAddrRow
|
||||
color: Style.accounts.backgroundExpanded
|
||||
|
||||
// icon level down
|
||||
Text {
|
||||
id: levelDown
|
||||
anchors {
|
||||
left : parent.left
|
||||
leftMargin : Style.accounts.leftMarginAddr
|
||||
verticalCenter : wrapAddr.verticalCenter
|
||||
}
|
||||
text : Style.fa.level_up
|
||||
font.family : Style.fontawesome.name
|
||||
color : Style.main.textDisabled
|
||||
rotation : 90
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: wrapAddr
|
||||
anchors {
|
||||
top : parent.top
|
||||
left : levelDown.right
|
||||
right : parent.right
|
||||
leftMargin : Style.main.leftMargin
|
||||
rightMargin : Style.main.rightMargin
|
||||
}
|
||||
height: Style.accounts.heightAddr
|
||||
border {
|
||||
width : Style.main.border
|
||||
color : Style.main.line
|
||||
}
|
||||
color: Style.accounts.backgroundAddrRow
|
||||
|
||||
TextMetrics {
|
||||
id: addressMetrics
|
||||
font: address.font
|
||||
elideWidth: 2*wrapAddr.width/3
|
||||
elide: Qt.ElideMiddle
|
||||
text: modelData
|
||||
}
|
||||
|
||||
Text {
|
||||
id: address
|
||||
anchors {
|
||||
verticalCenter : parent.verticalCenter
|
||||
left: parent.left
|
||||
leftMargin: Style.main.leftMargin
|
||||
}
|
||||
font.pointSize : Style.main.fontSize * Style.pt
|
||||
color: Style.main.text
|
||||
text: addressMetrics.elidedText
|
||||
}
|
||||
|
||||
ClickIconText {
|
||||
id: addressConfig
|
||||
anchors {
|
||||
verticalCenter : parent.verticalCenter
|
||||
right: parent.right
|
||||
rightMargin: Style.main.rightMargin
|
||||
}
|
||||
text : qsTr("Address configuration", "Display the IMAP/SMTP configuration for address")
|
||||
iconText : Style.fa.gear
|
||||
textColor : Style.main.textBlue
|
||||
onClicked : infoWin.showInfo(root.iAccount,index)
|
||||
|
||||
Accessible.description: qsTr("Address configuration for %1", "Accessible text of button displaying the IMAP/SMTP configuration for address %1").arg(modelData)
|
||||
Accessible.ignored: !enabled
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: clickSettings
|
||||
anchors.fill: wrapAddr
|
||||
onClicked : addressConfig.clicked()
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
hoverEnabled: true
|
||||
onPressed: {
|
||||
wrapAddr.color = Qt.rgba(1,1,1,0.20)
|
||||
}
|
||||
onEntered: {
|
||||
wrapAddr.color = Qt.rgba(1,1,1,0.15)
|
||||
}
|
||||
onExited: {
|
||||
wrapAddr.color = Style.accounts.backgroundAddrRow
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: line
|
||||
color: Style.accounts.line
|
||||
height: Style.accounts.heightLine
|
||||
width: root.row_width
|
||||
}
|
||||
|
||||
|
||||
states: [
|
||||
State {
|
||||
name: "connected"
|
||||
PropertyChanges {
|
||||
target : addressList
|
||||
model : listalias
|
||||
}
|
||||
PropertyChanges {
|
||||
target : toggleIcon
|
||||
color : Style.main.text
|
||||
}
|
||||
PropertyChanges {
|
||||
target : accountName
|
||||
color : Style.main.text
|
||||
}
|
||||
PropertyChanges {
|
||||
target : statusMark
|
||||
textColor : Style.main.textGreen
|
||||
text : qsTr("connected", "status of a listed logged-in account")
|
||||
iconText : Style.fa.circle
|
||||
}
|
||||
PropertyChanges {
|
||||
target : logoutAccount
|
||||
text : qsTr("Log out", "action to log out a connected account")
|
||||
onClicked : {
|
||||
mainaccRow.state="collapsed"
|
||||
dialogGlobal.input = root.iAccount
|
||||
dialogGlobal.state = "logout"
|
||||
dialogGlobal.show()
|
||||
dialogGlobal.confirmed()
|
||||
}
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "disconnected"
|
||||
PropertyChanges {
|
||||
target : addressList
|
||||
model : 0
|
||||
}
|
||||
PropertyChanges {
|
||||
target : toggleIcon
|
||||
color : Style.main.textDisabled
|
||||
}
|
||||
PropertyChanges {
|
||||
target : accountName
|
||||
color : Style.main.textDisabled
|
||||
}
|
||||
PropertyChanges {
|
||||
target : statusMark
|
||||
textColor : Style.main.textDisabled
|
||||
text : qsTr("disconnected", "status of a listed logged-out account")
|
||||
iconText : Style.fa.circle_o
|
||||
}
|
||||
PropertyChanges {
|
||||
target : logoutAccount
|
||||
text : qsTr("Log in", "action to log in a disconnected account")
|
||||
onClicked : {
|
||||
dialogAddUser.username = root.listalias[0]
|
||||
dialogAddUser.show()
|
||||
dialogAddUser.inputPassword.focusInput = true
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -1,72 +0,0 @@
|
||||
// Copyright (c) 2021 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
// ProtonMail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// ProtonMail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
// Dialog with main menu
|
||||
|
||||
import QtQuick 2.8
|
||||
import BridgeUI 1.0
|
||||
import ProtonUI 1.0
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
color: "#aaff5577"
|
||||
anchors {
|
||||
left : tabbar.left
|
||||
right : tabbar.right
|
||||
top : tabbar.bottom
|
||||
bottom : parent.bottom
|
||||
}
|
||||
visible: false
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: toggle()
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
color : Style.menu.background
|
||||
radius : Style.menu.radius
|
||||
width : Style.menu.width
|
||||
height : Style.menu.height
|
||||
anchors {
|
||||
top : parent.top
|
||||
right : parent.right
|
||||
topMargin : Style.menu.topMargin
|
||||
rightMargin : Style.menu.rightMargin
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
}
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: qsTr("About")
|
||||
color: Style.menu.text
|
||||
}
|
||||
}
|
||||
|
||||
function toggle(){
|
||||
if (root.visible == false) {
|
||||
root.visible = true
|
||||
} else {
|
||||
root.visible = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,124 +0,0 @@
|
||||
// Copyright (c) 2021 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
// ProtonMail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// ProtonMail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
// Dialog with Yes/No buttons
|
||||
|
||||
import QtQuick 2.8
|
||||
import ProtonUI 1.0
|
||||
|
||||
Dialog {
|
||||
id: root
|
||||
|
||||
title : ""
|
||||
isDialogBusy: false
|
||||
property string firstParagraph : qsTr("The Bridge is an application that runs on your computer in the background and seamlessly encrypts and decrypts your mail as it enters and leaves your computer.", "instructions that appear on welcome screen at first start")
|
||||
property string secondParagraph : qsTr("To add your ProtonMail account to the Bridge and <strong>generate your Bridge password</strong>, please see <a href=\"https://protonmail.com/bridge/install\">the installation guide</a> for detailed setup instructions.", "confirms and dismisses a notification (URL that leads to installation guide should stay intact)")
|
||||
|
||||
Column {
|
||||
id: dialogMessage
|
||||
property int heightInputs : welcome.height + middleSep.height + instructions.height + buttSep.height + buttonOkay.height + imageSep.height + logo.height
|
||||
|
||||
Rectangle { color : "transparent"; width : Style.main.dummy; height : (root.height-dialogMessage.heightInputs)/2 }
|
||||
|
||||
Text {
|
||||
id:welcome
|
||||
color: Style.main.text
|
||||
font.bold: true
|
||||
font.pointSize: 1.5*Style.main.fontSize*Style.pt
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
text: qsTr("Welcome to the", "welcome screen that appears on first start")
|
||||
}
|
||||
|
||||
Rectangle {id: imageSep; color : "transparent"; width : Style.main.dummy; height : Style.dialog.heightSeparator }
|
||||
|
||||
|
||||
Row {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
spacing: Style.dialog.spacing
|
||||
Image {
|
||||
id: logo
|
||||
anchors.bottom : pmbridge.baseline
|
||||
height : 2*Style.main.fontSize
|
||||
fillMode : Image.PreserveAspectFit
|
||||
mipmap : true
|
||||
source : "../ProtonUI/images/pm_logo.png"
|
||||
}
|
||||
AccessibleText {
|
||||
id:pmbridge
|
||||
color: Style.main.text
|
||||
font.bold: true
|
||||
font.pointSize: 2.2*Style.main.fontSize*Style.pt
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
text: qsTr("ProtonMail Bridge", "app title")
|
||||
|
||||
Accessible.name: this.clearText(pmbridge.text)
|
||||
Accessible.description: this.clearText(welcome.text+ " " + pmbridge.text + ". " + root.firstParagraph + ". " + root.secondParagraph)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
Rectangle { id:middleSep; color : "transparent"; width : Style.main.dummy; height : Style.dialog.heightSeparator }
|
||||
|
||||
|
||||
Text {
|
||||
id:instructions
|
||||
color: Style.main.text
|
||||
font.pointSize: Style.main.fontSize*Style.pt
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
width: root.width/1.5
|
||||
wrapMode: Text.Wrap
|
||||
textFormat: Text.RichText
|
||||
text: "<html><style>a { color: "+Style.main.textBlue+"; text-decoration: none;}</style><body>"+
|
||||
root.firstParagraph +
|
||||
"<br/><br/>"+
|
||||
root.secondParagraph +
|
||||
"</body></html>"
|
||||
onLinkActivated: {
|
||||
Qt.openUrlExternally(link)
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle { id:buttSep; color : "transparent"; width : Style.main.dummy; height : 2*Style.dialog.heightSeparator }
|
||||
|
||||
|
||||
ButtonRounded {
|
||||
id:buttonOkay
|
||||
color_main: Style.dialog.text
|
||||
color_minor: Style.main.textBlue
|
||||
isOpaque: true
|
||||
fa_icon: Style.fa.check
|
||||
text: qsTr("Okay", "confirms and dismisses a notification")
|
||||
onClicked : root.hide()
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
}
|
||||
|
||||
timer.interval : 3000
|
||||
|
||||
Connections {
|
||||
target: timer
|
||||
onTriggered: {
|
||||
}
|
||||
}
|
||||
|
||||
onShow : {
|
||||
pmbridge.Accessible.selected = true
|
||||
}
|
||||
}
|
||||
@ -1,194 +0,0 @@
|
||||
// Copyright (c) 2021 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
// ProtonMail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// ProtonMail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
// Change default keychain dialog
|
||||
|
||||
import QtQuick 2.8
|
||||
import BridgeUI 1.0
|
||||
import ProtonUI 1.0
|
||||
import QtQuick.Controls 2.2 as QC
|
||||
import QtQuick.Layouts 1.0
|
||||
|
||||
Dialog {
|
||||
id: root
|
||||
|
||||
title : "Change which keychain Bridge uses as default"
|
||||
subtitle : "Select which keychain is used (Bridge will automatically restart)"
|
||||
isDialogBusy: currentIndex==1
|
||||
|
||||
property var selectedKeychain
|
||||
|
||||
Connections {
|
||||
target: go.selectedKeychain
|
||||
onValueChanged: {
|
||||
console.debug("go.selectedKeychain == ", go.selectedKeychain)
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.minimumHeight: root.titleHeight + Style.dialog.heightSeparator
|
||||
Layout.maximumHeight: root.titleHeight + Style.dialog.heightSeparator
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
|
||||
ColumnLayout {
|
||||
anchors.centerIn: parent
|
||||
|
||||
Repeater {
|
||||
id: keychainRadioButtons
|
||||
model: go.availableKeychain
|
||||
QC.RadioButton {
|
||||
id: radioDelegate
|
||||
text: modelData
|
||||
checked: go.selectedKeychain === modelData
|
||||
|
||||
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
|
||||
spacing: Style.main.spacing
|
||||
|
||||
indicator: Text {
|
||||
text : radioDelegate.checked ? Style.fa.check_circle : Style.fa.circle_o
|
||||
color : radioDelegate.checked ? Style.main.textBlue : Style.main.textInactive
|
||||
font {
|
||||
pointSize: Style.dialog.iconSize * Style.pt
|
||||
family: Style.fontawesome.name
|
||||
}
|
||||
}
|
||||
contentItem: Text {
|
||||
text: radioDelegate.text
|
||||
color: Style.main.text
|
||||
font {
|
||||
pointSize: Style.dialog.fontSize * Style.pt
|
||||
bold: checked
|
||||
}
|
||||
horizontalAlignment : Text.AlignHCenter
|
||||
verticalAlignment : Text.AlignVCenter
|
||||
leftPadding: Style.dialog.iconSize
|
||||
}
|
||||
|
||||
onCheckedChanged: {
|
||||
if (checked) {
|
||||
root.selectedKeychain = modelData
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.minimumHeight: Style.dialog.heightSeparator
|
||||
Layout.maximumHeight: Style.dialog.heightSeparator
|
||||
}
|
||||
|
||||
Row {
|
||||
id: buttonRow
|
||||
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
|
||||
spacing: Style.dialog.spacing
|
||||
ButtonRounded {
|
||||
id:buttonNo
|
||||
color_main: Style.dialog.text
|
||||
fa_icon: Style.fa.times
|
||||
text: qsTr("Cancel", "dismisses current action")
|
||||
onClicked : root.hide()
|
||||
}
|
||||
ButtonRounded {
|
||||
id: buttonYes
|
||||
color_main: Style.dialog.text
|
||||
color_minor: Style.main.textBlue
|
||||
isOpaque: true
|
||||
fa_icon: Style.fa.check
|
||||
text: qsTr("Okay", "confirms and dismisses a notification")
|
||||
onClicked : root.confirmed()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.minimumHeight: root.titleHeight + Style.dialog.heightSeparator
|
||||
Layout.maximumHeight: root.titleHeight + Style.dialog.heightSeparator
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
|
||||
|
||||
Text {
|
||||
id: answ
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width : parent.width/2
|
||||
color: Style.dialog.text
|
||||
font {
|
||||
pointSize : Style.dialog.fontSize * Style.pt
|
||||
bold : true
|
||||
}
|
||||
text : "Default keychain is now set to " + root.selectedKeychain +
|
||||
"\n\n" +
|
||||
qsTr("Settings will be applied after the next start.", "notification about setting being applied after next start") +
|
||||
"\n\n" +
|
||||
qsTr("Bridge will now restart.", "notification about restarting")
|
||||
wrapMode: Text.Wrap
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Shortcut {
|
||||
sequence: StandardKey.Cancel
|
||||
onActivated: root.hide()
|
||||
}
|
||||
|
||||
Shortcut {
|
||||
sequence: "Enter"
|
||||
onActivated: root.confirmed()
|
||||
}
|
||||
|
||||
function confirmed() {
|
||||
if (selectedKeychain === go.selectedKeychain) {
|
||||
root.hide()
|
||||
return
|
||||
}
|
||||
|
||||
incrementCurrentIndex()
|
||||
timer.start()
|
||||
}
|
||||
|
||||
timer.interval : 5000
|
||||
|
||||
Connections {
|
||||
target: timer
|
||||
onTriggered: {
|
||||
// This action triggers restart on the backend side.
|
||||
go.selectedKeychain = selectedKeychain
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,233 +0,0 @@
|
||||
// Copyright (c) 2021 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
// ProtonMail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// ProtonMail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
// Dialog with Yes/No buttons
|
||||
|
||||
import QtQuick 2.8
|
||||
import BridgeUI 1.0
|
||||
import ProtonUI 1.0
|
||||
import QtQuick.Controls 2.2 as QC
|
||||
|
||||
Dialog {
|
||||
id: root
|
||||
|
||||
title : "Set IMAP & SMTP settings"
|
||||
subtitle : "Changes require reconfiguration of Mail client. (Bridge will automatically restart)"
|
||||
isDialogBusy: currentIndex==1
|
||||
|
||||
Column {
|
||||
id: dialogMessage
|
||||
property int heightInputs : imapPort.height + middleSep.height + smtpPort.height + buttonSep.height + buttonRow.height + secSMTPSep.height + securitySMTP.height
|
||||
|
||||
Rectangle { color : "transparent"; width : Style.main.dummy; height : (root.height-dialogMessage.heightInputs)/1.6 }
|
||||
|
||||
InputField {
|
||||
id: imapPort
|
||||
iconText : Style.fa.hashtag
|
||||
label : qsTr("IMAP port", "entry field to choose port used for the IMAP server")
|
||||
text : "undef"
|
||||
}
|
||||
|
||||
Rectangle { id:middleSep; color : "transparent"; width : Style.main.dummy; height : Style.dialog.heightSeparator }
|
||||
|
||||
InputField {
|
||||
id: smtpPort
|
||||
iconText : Style.fa.hashtag
|
||||
label : qsTr("SMTP port", "entry field to choose port used for the SMTP server")
|
||||
text : "undef"
|
||||
}
|
||||
|
||||
Rectangle { id:secSMTPSep; color : Style.transparent; width : Style.main.dummy; height : Style.dialog.heightSeparator }
|
||||
|
||||
// SSL button group
|
||||
Rectangle {
|
||||
anchors.horizontalCenter : parent.horizontalCenter
|
||||
width : Style.dialog.widthInput
|
||||
height : securitySMTPLabel.height + securitySMTP.height
|
||||
color : "transparent"
|
||||
|
||||
AccessibleText {
|
||||
id: securitySMTPLabel
|
||||
anchors.left : parent.left
|
||||
text:qsTr("SMTP connection mode")
|
||||
color: Style.dialog.text
|
||||
font {
|
||||
pointSize : Style.dialog.fontSize * Style.pt
|
||||
bold : true
|
||||
}
|
||||
}
|
||||
|
||||
QC.ButtonGroup {
|
||||
buttons: securitySMTP.children
|
||||
}
|
||||
Row {
|
||||
id: securitySMTP
|
||||
spacing: Style.dialog.spacing
|
||||
anchors.top: securitySMTPLabel.bottom
|
||||
anchors.topMargin: Style.dialog.fontSize
|
||||
|
||||
CheckBoxLabel {
|
||||
id: securitySMTPSSL
|
||||
text: qsTr("SSL")
|
||||
}
|
||||
|
||||
CheckBoxLabel {
|
||||
checked: true
|
||||
id: securitySMTPSTARTTLS
|
||||
text: qsTr("STARTTLS")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle { id:buttonSep; color : "transparent"; width : Style.main.dummy; height : 2*Style.dialog.heightSeparator }
|
||||
|
||||
Row {
|
||||
id: buttonRow
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
spacing: Style.dialog.spacing
|
||||
ButtonRounded {
|
||||
id:buttonNo
|
||||
color_main: Style.dialog.text
|
||||
fa_icon: Style.fa.times
|
||||
text: qsTr("Cancel", "dismisses current action")
|
||||
onClicked : root.hide()
|
||||
}
|
||||
ButtonRounded {
|
||||
id: buttonYes
|
||||
color_main: Style.dialog.text
|
||||
color_minor: Style.main.textBlue
|
||||
isOpaque: true
|
||||
fa_icon: Style.fa.check
|
||||
text: qsTr("Okay", "confirms and dismisses a notification")
|
||||
onClicked : root.confirmed()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
Rectangle { color : "transparent"; width : Style.main.dummy; height : (root.height-answ.height)/2 }
|
||||
Text {
|
||||
id: answ
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
width : parent.width/2
|
||||
color: Style.dialog.text
|
||||
font {
|
||||
pointSize : Style.dialog.fontSize * Style.pt
|
||||
bold : true
|
||||
}
|
||||
text : "IMAP: " + imapPort.text + "\nSMTP: " + smtpPort.text + "\nSMTP Connection Mode: " + getSelectedSSLMode() + "\n\n" +
|
||||
qsTr("Settings will be applied after the next start. You will need to reconfigure your email client(s).", "after user changes their ports they will see this notification to reconfigure their setup") +
|
||||
"\n\n" +
|
||||
qsTr("Bridge will now restart.", "after user changes their ports this appears to notify the user of restart")
|
||||
wrapMode: Text.Wrap
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
}
|
||||
|
||||
function areInputsOK() {
|
||||
var isOK = true
|
||||
var imapUnchanged = false
|
||||
var secSMTPUnchanged = (securitySMTPSTARTTLS.checked == go.isSMTPSTARTTLS())
|
||||
root.warning.text = ""
|
||||
|
||||
if (imapPort.text!=go.getIMAPPort()) {
|
||||
if (go.isPortOpen(imapPort.text)!=0) {
|
||||
imapPort.rightIcon = Style.fa.exclamation_triangle
|
||||
root.warning.text = qsTr("Port number is not available.", "if the user changes one of their ports to a port that is occupied by another application")
|
||||
isOK=false
|
||||
} else {
|
||||
imapPort.rightIcon = Style.fa.check_circle
|
||||
}
|
||||
} else {
|
||||
imapPort.rightIcon = ""
|
||||
imapUnchanged = true
|
||||
}
|
||||
|
||||
if (smtpPort.text!=go.getSMTPPort()) {
|
||||
if (go.isPortOpen(smtpPort.text)!=0) {
|
||||
smtpPort.rightIcon = Style.fa.exclamation_triangle
|
||||
root.warning.text = qsTr("Port number is not available.", "if the user changes one of their ports to a port that is occupied by another application")
|
||||
isOK=false
|
||||
} else {
|
||||
smtpPort.rightIcon = Style.fa.check_circle
|
||||
}
|
||||
} else {
|
||||
smtpPort.rightIcon = ""
|
||||
if (imapUnchanged && secSMTPUnchanged) {
|
||||
root.warning.text = qsTr("Please change at least one port number or SMTP security.", "if the user tries to change IMAP/SMTP ports to the same ports as before")
|
||||
isOK=false
|
||||
}
|
||||
}
|
||||
|
||||
if (imapPort.text == smtpPort.text) {
|
||||
smtpPort.rightIcon = Style.fa.exclamation_triangle
|
||||
root.warning.text = qsTr("Port numbers must be different.", "if the user sets both the IMAP and SMTP ports to the same number")
|
||||
isOK=false
|
||||
}
|
||||
|
||||
root.warning.visible = !isOK
|
||||
return isOK
|
||||
}
|
||||
|
||||
function confirmed() {
|
||||
if (areInputsOK()) {
|
||||
incrementCurrentIndex()
|
||||
timer.start()
|
||||
}
|
||||
}
|
||||
|
||||
function getSelectedSSLMode() {
|
||||
if (securitySMTPSTARTTLS.checked == true) {
|
||||
return "STARTTLS"
|
||||
} else {
|
||||
return "SSL"
|
||||
}
|
||||
}
|
||||
|
||||
onShow : {
|
||||
imapPort.text = go.getIMAPPort()
|
||||
smtpPort.text = go.getSMTPPort()
|
||||
if (go.isSMTPSTARTTLS()) {
|
||||
securitySMTPSTARTTLS.checked = true
|
||||
} else {
|
||||
securitySMTPSSL.checked = true
|
||||
}
|
||||
areInputsOK()
|
||||
root.warning.visible = false
|
||||
}
|
||||
|
||||
Shortcut {
|
||||
sequence: StandardKey.Cancel
|
||||
onActivated: root.hide()
|
||||
}
|
||||
|
||||
Shortcut {
|
||||
sequence: "Enter"
|
||||
onActivated: root.confirmed()
|
||||
}
|
||||
|
||||
timer.interval : 3000
|
||||
|
||||
Connections {
|
||||
target: timer
|
||||
onTriggered: {
|
||||
go.setPortsAndSecurity(imapPort.text, smtpPort.text, securitySMTPSTARTTLS.checked)
|
||||
go.setToRestart()
|
||||
Qt.quit()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,77 +0,0 @@
|
||||
// Copyright (c) 2021 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
// ProtonMail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// ProtonMail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import QtQuick 2.8
|
||||
import BridgeUI 1.0
|
||||
import ProtonUI 1.0
|
||||
|
||||
|
||||
Dialog {
|
||||
id: root
|
||||
title: qsTr("Connection security error", "Title of modal explainning TLS issue")
|
||||
|
||||
property string par1Title : qsTr("Description:", "Title of paragraph describing the issue")
|
||||
property string par1Text : qsTr (
|
||||
"ProtonMail Bridge was not able to establish a secure connection to Proton servers due to a TLS certificate error. "+
|
||||
"This means your connection may potentially be insecure and susceptible to monitoring by third parties.",
|
||||
"A paragraph describing the issue"
|
||||
)
|
||||
|
||||
property string par2Title : qsTr("Recommendation:", "Title of paragraph describing recommended steps")
|
||||
property string par2Text : qsTr (
|
||||
"If you are on a corporate or public network, the network administrator may be monitoring or intercepting all traffic.",
|
||||
"A paragraph describing network issue"
|
||||
)
|
||||
property string par2ul1 : qsTr(
|
||||
"If you trust your network operator, you can continue to use ProtonMail as usual.",
|
||||
"A list item describing recomendation for trusted network"
|
||||
)
|
||||
|
||||
property string par2ul2 : qsTr(
|
||||
"If you don't trust your network operator, reconnect to ProtonMail over a VPN (such as ProtonVPN) "+
|
||||
"which encrypts your Internet connection, or use a different network to access ProtonMail.",
|
||||
"A list item describing recomendation for untrusted network"
|
||||
)
|
||||
property string par3Text : qsTr("Learn more on our knowledge base article","A paragraph describing where to find more information")
|
||||
property string kbArticleText : qsTr("What is TLS certificate error.", "Link text for knowledge base article")
|
||||
property string kbArticleLink : "https://protonmail.com/support/knowledge-base/"
|
||||
|
||||
|
||||
Item {
|
||||
AccessibleText {
|
||||
anchors.centerIn: parent
|
||||
color: Style.old.pm_white
|
||||
linkColor: color
|
||||
width: parent.width - 50 * Style.px
|
||||
wrapMode: Text.WordWrap
|
||||
font.pointSize: Style.main.fontSize*Style.pt
|
||||
onLinkActivated: Qt.openUrlExternally(link)
|
||||
text: "<h3>"+par1Title+"</h3>"+
|
||||
par1Text+"<br>\n"+
|
||||
"<h3>"+par2Title+"</h3>"+
|
||||
par2Text+
|
||||
"<ul>"+
|
||||
"<li>"+par2ul1+"</li>"+
|
||||
"<li>"+par2ul2+"</li>"+
|
||||
"</ul>"+"<br>\n"+
|
||||
""
|
||||
//par3Text+
|
||||
//" <a href='"+kbArticleLink+"'>"+kbArticleText+"</a>\n"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,403 +0,0 @@
|
||||
// Copyright (c) 2021 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
// ProtonMail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// ProtonMail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
// Dialog with Yes/No buttons
|
||||
|
||||
import QtQuick 2.8
|
||||
import BridgeUI 1.0
|
||||
import ProtonUI 1.0
|
||||
|
||||
Dialog {
|
||||
id: root
|
||||
|
||||
title : ""
|
||||
|
||||
property string input
|
||||
|
||||
property alias question : msg.text
|
||||
property alias note : noteText.text
|
||||
property alias answer : answ.text
|
||||
property alias buttonYes : buttonYes
|
||||
property alias buttonNo : buttonNo
|
||||
|
||||
isDialogBusy: currentIndex==1
|
||||
|
||||
signal confirmed()
|
||||
|
||||
Column {
|
||||
id: dialogMessage
|
||||
property int heightInputs : msg.height+
|
||||
middleSep.height+
|
||||
buttonRow.height +
|
||||
(checkboxSep.visible ? checkboxSep.height : 0 ) +
|
||||
(noteSep.visible ? noteSep.height : 0 ) +
|
||||
(checkBoxWrapper.visible ? checkBoxWrapper.height : 0 ) +
|
||||
(root.note!="" ? noteText.height : 0 )
|
||||
|
||||
Rectangle { color : "transparent"; width : Style.main.dummy; height : (root.height-dialogMessage.heightInputs)/2 }
|
||||
|
||||
AccessibleText {
|
||||
id:noteText
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
color: Style.dialog.text
|
||||
font {
|
||||
pointSize: Style.dialog.fontSize * Style.pt
|
||||
bold: false
|
||||
}
|
||||
width: 2*root.width/3
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
Rectangle { id: noteSep; visible: note!=""; color : "transparent"; width : Style.main.dummy; height : Style.dialog.heightSeparator}
|
||||
|
||||
AccessibleText {
|
||||
id: msg
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
color: Style.dialog.text
|
||||
font {
|
||||
pointSize: Style.dialog.fontSize * Style.pt
|
||||
bold: true
|
||||
}
|
||||
width: 2*parent.width/3
|
||||
text : ""
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
|
||||
Rectangle { id: checkboxSep; visible: checkBoxWrapper.visible; color : "transparent"; width : Style.main.dummy; height : Style.dialog.heightSeparator}
|
||||
Row {
|
||||
id: checkBoxWrapper
|
||||
property bool isChecked : false
|
||||
visible: root.state=="deleteUser"
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
spacing: Style.dialog.spacing
|
||||
|
||||
function toggle() {
|
||||
checkBoxWrapper.isChecked = !checkBoxWrapper.isChecked
|
||||
}
|
||||
|
||||
Text {
|
||||
id: checkbox
|
||||
font {
|
||||
pointSize : Style.dialog.iconSize * Style.pt
|
||||
family : Style.fontawesome.name
|
||||
}
|
||||
anchors.verticalCenter : parent.verticalCenter
|
||||
text: checkBoxWrapper.isChecked ? Style.fa.check_square_o : Style.fa.square_o
|
||||
color: checkBoxWrapper.isChecked ? Style.main.textBlue : Style.main.text
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onPressed: checkBoxWrapper.toggle()
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
}
|
||||
}
|
||||
Text {
|
||||
id: checkBoxNote
|
||||
anchors.verticalCenter : parent.verticalCenter
|
||||
text: qsTr("Additionally delete all stored preferences and data", "when removing an account, this extra preference additionally deletes all cached data")
|
||||
color: Style.main.text
|
||||
font.pointSize: Style.dialog.fontSize * Style.pt
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onPressed: checkBoxWrapper.toggle()
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
Accessible.role: Accessible.CheckBox
|
||||
Accessible.checked: checkBoxWrapper.isChecked
|
||||
Accessible.name: checkBoxNote.text
|
||||
Accessible.description: checkBoxNote.text
|
||||
Accessible.ignored: checkBoxNote.text == ""
|
||||
Accessible.onToggleAction: checkBoxWrapper.toggle()
|
||||
Accessible.onPressAction: checkBoxWrapper.toggle()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle { id: middleSep; color : "transparent"; width : Style.main.dummy; height : 2*Style.dialog.heightSeparator }
|
||||
|
||||
Row {
|
||||
id: buttonRow
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
spacing: Style.dialog.spacing
|
||||
ButtonRounded {
|
||||
id:buttonNo
|
||||
visible: root.state != "toggleEarlyAccess"
|
||||
color_main: Style.dialog.text
|
||||
fa_icon: Style.fa.times
|
||||
text: qsTr("No")
|
||||
onClicked : root.hide()
|
||||
}
|
||||
ButtonRounded {
|
||||
id: buttonYes
|
||||
color_main: Style.dialog.text
|
||||
color_minor: Style.main.textBlue
|
||||
isOpaque: true
|
||||
fa_icon: Style.fa.check
|
||||
text: root.state == "toggleEarlyAccess" ? qsTr("Ok") : qsTr("Yes")
|
||||
onClicked : {
|
||||
currentIndex=1
|
||||
root.confirmed()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Column {
|
||||
Rectangle { color : "transparent"; width : Style.main.dummy; height : (root.height-answ.height)/2 }
|
||||
AccessibleText {
|
||||
id: answ
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
color: Style.old.pm_white
|
||||
font {
|
||||
pointSize : Style.dialog.fontSize * Style.pt
|
||||
bold : true
|
||||
}
|
||||
width: 3*parent.width/4
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
text : qsTr("Waiting...", "in general this displays between screens when processing data takes a long time")
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
states : [
|
||||
State {
|
||||
name: "quit"
|
||||
PropertyChanges {
|
||||
target: root
|
||||
currentIndex : 0
|
||||
title : qsTr("Close Bridge", "quits the application")
|
||||
question : qsTr("Are you sure you want to close the Bridge?", "asked when user tries to quit the application")
|
||||
note : ""
|
||||
answer : qsTr("Closing Bridge...", "displayed when user is quitting application")
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "logout"
|
||||
PropertyChanges {
|
||||
target: root
|
||||
currentIndex : 1
|
||||
title : qsTr("Logout", "title of page that displays during account logout")
|
||||
question : ""
|
||||
note : ""
|
||||
answer : qsTr("Logging out...", "displays during account logout")
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "deleteUser"
|
||||
PropertyChanges {
|
||||
target: root
|
||||
currentIndex : 0
|
||||
title : qsTr("Delete account", "title of page that displays during account deletion")
|
||||
question : qsTr("Are you sure you want to remove this account?", "displays during account deletion")
|
||||
note : ""
|
||||
answer : qsTr("Deleting ...", "displays during account deletion")
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "clearChain"
|
||||
PropertyChanges {
|
||||
target : root
|
||||
currentIndex : 0
|
||||
title : qsTr("Clear keychain", "title of page that displays during keychain clearing")
|
||||
question : qsTr("Are you sure you want to clear your keychain?", "displays during keychain clearing")
|
||||
note : qsTr("This will remove all accounts that you have added to the Bridge and disconnect you from your email client(s).", "displays during keychain clearing")
|
||||
answer : qsTr("Clearing the keychain ...", "displays during keychain clearing")
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "clearCache"
|
||||
PropertyChanges {
|
||||
target: root
|
||||
currentIndex : 0
|
||||
title : qsTr("Clear cache", "title of page that displays during cache clearing")
|
||||
question : qsTr("Are you sure you want to clear your local cache?", "displays during cache clearing")
|
||||
note : qsTr("This will delete all of your stored preferences as well as cached email data for all accounts, and requires you to reconfigure your client.", "displays during cache clearing")
|
||||
answer : qsTr("Clearing the cache ...", "displays during cache clearing")
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "checkUpdates"
|
||||
PropertyChanges {
|
||||
target: root
|
||||
currentIndex : 1
|
||||
title : ""
|
||||
question : ""
|
||||
note : ""
|
||||
answer : qsTr("Checking for updates ...", "displays if user clicks the Check for Updates button in the Help tab")
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "addressmode"
|
||||
PropertyChanges {
|
||||
target: root
|
||||
currentIndex : 0
|
||||
title : ""
|
||||
question : qsTr("Do you want to continue?", "asked when the user changes between split and combined address mode")
|
||||
note : qsTr("Changing between split and combined address mode will require you to delete your account(s) from your email client and begin the setup process from scratch.", "displayed when the user changes between split and combined address mode")
|
||||
answer : qsTr("Configuring address mode...", "displayed when the user changes between split and combined address mode")
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "toggleAutoStart"
|
||||
PropertyChanges {
|
||||
target: root
|
||||
currentIndex : 1
|
||||
question : ""
|
||||
note : ""
|
||||
title : ""
|
||||
answer : {
|
||||
var msgTurnOn = qsTr("Turning on automatic start of Bridge...", "when the automatic start feature is selected")
|
||||
var msgTurnOff = qsTr("Turning off automatic start of Bridge...", "when the automatic start feature is deselected")
|
||||
return go.isAutoStart==false ? msgTurnOff : msgTurnOn
|
||||
}
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "toggleAllowProxy"
|
||||
PropertyChanges {
|
||||
target: root
|
||||
currentIndex : 0
|
||||
question : {
|
||||
var questionTurnOn = qsTr("Do you want to allow alternative routing?")
|
||||
var questionTurnOff = qsTr("Do you want to disallow alternative routing?")
|
||||
return go.isProxyAllowed==false ? questionTurnOn : questionTurnOff
|
||||
}
|
||||
note : qsTr("In case Proton sites are blocked, this setting allows Bridge to try alternative network routing to reach Proton, which can be useful for bypassing firewalls or network issues. We recommend keeping this setting on for greater reliability.")
|
||||
title : {
|
||||
var titleTurnOn = qsTr("Allow alternative routing")
|
||||
var titleTurnOff = qsTr("Disallow alternative routing")
|
||||
return go.isProxyAllowed==false ? titleTurnOn : titleTurnOff
|
||||
}
|
||||
answer : {
|
||||
var msgTurnOn = qsTr("Allowing Bridge to use alternative routing to connect to Proton...", "when the allow proxy feature is selected")
|
||||
var msgTurnOff = qsTr("Disallowing Bridge to use alternative routing to connect to Proton...", "when the allow proxy feature is deselected")
|
||||
return go.isProxyAllowed==false ? msgTurnOn : msgTurnOff
|
||||
}
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "toggleEarlyAccessOn"
|
||||
PropertyChanges {
|
||||
target: root
|
||||
currentIndex : 0
|
||||
question : qsTr("Do you want to be the first to get the latest updates? Please keep in mind that early versions may be less stable.")
|
||||
note : ""
|
||||
title : qsTr("Enable early access")
|
||||
answer : qsTr("Enabling early access...")
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "toggleEarlyAccessOff"
|
||||
PropertyChanges {
|
||||
target: root
|
||||
currentIndex : 0
|
||||
question : qsTr("Are you sure you want to leave early access? Please keep in mind this operation clears the cache and restarts Bridge.")
|
||||
note : qsTr("This will delete all of your stored preferences as well as cached email data for all accounts, and requires you to reconfigure your client.")
|
||||
title : qsTr("Disable early access")
|
||||
answer : qsTr("Disabling early access...")
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "noKeychain"
|
||||
PropertyChanges {
|
||||
target: root
|
||||
currentIndex : 0
|
||||
note : qsTr(
|
||||
"%1 is not able to detected a supported password manager (pass, gnome-keyring). Please install and setup supported password manager and restart the application.",
|
||||
"Error message when no keychain is detected"
|
||||
).arg(go.programTitle)
|
||||
question : qsTr("Do you want to close application now?", "when no password manager found." )
|
||||
title : "No system password manager detected"
|
||||
answer : qsTr("Closing Bridge...", "displayed when user is quitting application")
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "undef";
|
||||
PropertyChanges {
|
||||
target: root
|
||||
currentIndex : 1
|
||||
question : ""
|
||||
note : ""
|
||||
title : ""
|
||||
answer : ""
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
Shortcut {
|
||||
sequence: StandardKey.Cancel
|
||||
onActivated: root.hide()
|
||||
}
|
||||
|
||||
Shortcut {
|
||||
sequence: "Enter"
|
||||
onActivated: root.confirmed()
|
||||
}
|
||||
|
||||
onHide: {
|
||||
checkBoxWrapper.isChecked = false
|
||||
state = "undef"
|
||||
}
|
||||
|
||||
onShow: {
|
||||
// hide all other dialogs
|
||||
winMain.dialogAddUser .visible = false
|
||||
winMain.dialogChangePort .visible = false
|
||||
winMain.dialogCredits .visible = false
|
||||
root.visible = true
|
||||
}
|
||||
|
||||
onConfirmed : {
|
||||
if (state == "quit" || state == "instance exists" ) {
|
||||
timer.interval = 1000
|
||||
} else {
|
||||
timer.interval = 300
|
||||
}
|
||||
answ.forceActiveFocus()
|
||||
timer.start()
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: timer
|
||||
onTriggered: {
|
||||
if ( state == "addressmode" ) { go.switchAddressMode (input) }
|
||||
if ( state == "clearChain" ) { go.clearKeychain () }
|
||||
if ( state == "clearCache" ) { go.clearCache () }
|
||||
if ( state == "deleteUser" ) { go.deleteAccount (input, checkBoxWrapper.isChecked) }
|
||||
if ( state == "logout" ) { go.logoutAccount (input) }
|
||||
if ( state == "toggleAutoStart" ) { go.toggleAutoStart () }
|
||||
if ( state == "toggleAllowProxy" ) { go.toggleAllowProxy () }
|
||||
if ( state == "toggleEarlyAccessOn" ) { go.toggleEarlyAccess () }
|
||||
if ( state == "toggleEarlyAccessOff" ) { go.toggleEarlyAccess () }
|
||||
if ( state == "quit" ) { Qt.quit () }
|
||||
if ( state == "instance exists" ) { Qt.quit () }
|
||||
if ( state == "noKeychain" ) { Qt.quit () }
|
||||
if ( state == "checkUpdates" ) { }
|
||||
}
|
||||
}
|
||||
|
||||
Keys.onPressed: {
|
||||
if (event.key == Qt.Key_Enter) {
|
||||
root.confirmed()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,143 +0,0 @@
|
||||
// Copyright (c) 2021 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
// ProtonMail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// ProtonMail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
// List the settings
|
||||
|
||||
import QtQuick 2.8
|
||||
import BridgeUI 1.0
|
||||
import ProtonUI 1.0
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
// must have wrapper
|
||||
Rectangle {
|
||||
id: wrapper
|
||||
anchors.centerIn: parent
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
color: Style.main.background
|
||||
|
||||
// content
|
||||
Column {
|
||||
anchors.horizontalCenter : parent.horizontalCenter
|
||||
|
||||
ButtonIconText {
|
||||
id: logs
|
||||
anchors.left: parent.left
|
||||
text: qsTr("Logs", "title of button that takes user to logs directory")
|
||||
leftIcon.text : Style.fa.align_justify
|
||||
rightIcon.text : Style.fa.chevron_circle_right
|
||||
rightIcon.font.pointSize : Style.settings.toggleSize * Style.pt
|
||||
onClicked: go.openLogs()
|
||||
}
|
||||
|
||||
ButtonIconText {
|
||||
id: bugreport
|
||||
anchors.left: parent.left
|
||||
text: qsTr("Report Bug", "title of button that takes user to bug report form")
|
||||
leftIcon.text : Style.fa.bug
|
||||
rightIcon.text : Style.fa.chevron_circle_right
|
||||
rightIcon.font.pointSize : Style.settings.toggleSize * Style.pt
|
||||
onClicked: bugreportWin.show()
|
||||
}
|
||||
|
||||
ButtonIconText {
|
||||
id: manual
|
||||
anchors.left: parent.left
|
||||
text: qsTr("Setup Guide", "title of button that opens setup and installation guide")
|
||||
leftIcon.text : Style.fa.book
|
||||
rightIcon.text : Style.fa.chevron_circle_right
|
||||
rightIcon.font.pointSize : Style.settings.toggleSize * Style.pt
|
||||
onClicked: go.openManual()
|
||||
}
|
||||
|
||||
ButtonIconText {
|
||||
id: updates
|
||||
anchors.left: parent.left
|
||||
text: qsTr("Check for Updates", "title of button to check for any app updates")
|
||||
leftIcon.text : Style.fa.refresh
|
||||
rightIcon.text : Style.fa.chevron_circle_right
|
||||
rightIcon.font.pointSize : Style.settings.toggleSize * Style.pt
|
||||
onClicked: {
|
||||
go.checkForUpdates()
|
||||
}
|
||||
}
|
||||
|
||||
// Bottom version notes
|
||||
Rectangle {
|
||||
anchors.horizontalCenter : parent.horizontalCenter
|
||||
height: viewAccount.separatorNoAccount - 3.2*manual.height
|
||||
width: wrapper.width
|
||||
color : "transparent"
|
||||
AccessibleText {
|
||||
anchors {
|
||||
bottom: parent.bottom
|
||||
horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
color: Style.main.textDisabled
|
||||
horizontalAlignment: Qt.AlignHCenter
|
||||
font.pointSize : Style.main.fontSize * Style.pt
|
||||
text:
|
||||
"ProtonMail Bridge "+go.getBackendVersion()+"\n"+
|
||||
"© 2020 Proton Technologies AG"
|
||||
}
|
||||
}
|
||||
Row {
|
||||
anchors.left : parent.left
|
||||
|
||||
Rectangle { height: Style.dialog.spacing; width: (wrapper.width - credits.width - licenseFile.width - release.width - sepaCreditsRelease.width)/2; color: "transparent"}
|
||||
|
||||
ClickIconText {
|
||||
id:credits
|
||||
iconText : ""
|
||||
text : qsTr("Credits", "link to click on to view list of credited libraries")
|
||||
textColor : Style.main.textDisabled
|
||||
fontSize : Style.main.fontSize
|
||||
textUnderline : true
|
||||
onClicked : winMain.dialogCredits.show()
|
||||
}
|
||||
|
||||
Rectangle {id: sepaLicenseFile ; height: Style.dialog.spacing; width: Style.main.dummy; color: "transparent"}
|
||||
|
||||
ClickIconText {
|
||||
id:licenseFile
|
||||
iconText : ""
|
||||
text : qsTr("License", "link to click on to view license file")
|
||||
textColor : Style.main.textDisabled
|
||||
fontSize : Style.main.fontSize
|
||||
textUnderline : true
|
||||
onClicked : {
|
||||
go.openLicenseFile()
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {id: sepaCreditsRelease ; height: Style.dialog.spacing; width: Style.main.dummy; color: "transparent"}
|
||||
|
||||
ClickIconText {
|
||||
id:release
|
||||
iconText : ""
|
||||
text : qsTr("Release notes", "link to click on to view release notes for this version of the app")
|
||||
textColor : Style.main.textDisabled
|
||||
fontSize : Style.main.fontSize
|
||||
textUnderline : true
|
||||
onClicked : gui.openReleaseNotes()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,144 +0,0 @@
|
||||
// Copyright (c) 2021 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
// ProtonMail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// ProtonMail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
// Window for imap and smtp settings
|
||||
|
||||
import QtQuick 2.8
|
||||
import QtQuick.Window 2.2
|
||||
import BridgeUI 1.0
|
||||
import ProtonUI 1.0
|
||||
|
||||
|
||||
Window {
|
||||
id:root
|
||||
width : Style.info.width
|
||||
height : Style.info.height
|
||||
minimumWidth : Style.info.width
|
||||
minimumHeight : Style.info.height
|
||||
maximumWidth : Style.info.width
|
||||
maximumHeight : Style.info.height
|
||||
color: "transparent"
|
||||
flags : Qt.Window | Qt.Dialog | Qt.FramelessWindowHint
|
||||
title : address
|
||||
|
||||
Accessible.role: Accessible.Window
|
||||
Accessible.name: qsTr("Configuration information for %1").arg(address)
|
||||
Accessible.description: Accessible.name
|
||||
|
||||
property QtObject accData : QtObject { // avoid null-pointer error
|
||||
property string account : "undef"
|
||||
property string aliases : "undef"
|
||||
property string hostname : "undef"
|
||||
property string password : "undef"
|
||||
property int portIMAP : 0
|
||||
property int portSMTP : 0
|
||||
}
|
||||
property string address : "undef"
|
||||
property int indexAccount : 0
|
||||
property int indexAddress : 0
|
||||
|
||||
WindowTitleBar {
|
||||
id: titleBar
|
||||
window: root
|
||||
}
|
||||
|
||||
Rectangle { // background
|
||||
color: Style.main.background
|
||||
anchors {
|
||||
left : parent.left
|
||||
right : parent.right
|
||||
top : titleBar.bottom
|
||||
bottom : parent.bottom
|
||||
}
|
||||
border {
|
||||
width: Style.main.border
|
||||
color: Style.tabbar.background
|
||||
}
|
||||
}
|
||||
|
||||
// info content
|
||||
Column {
|
||||
anchors {
|
||||
left: parent.left
|
||||
top: titleBar.bottom
|
||||
leftMargin: Style.main.leftMargin
|
||||
topMargin: Style.info.topMargin
|
||||
}
|
||||
width : root.width - Style.main.leftMargin - Style.main.rightMargin
|
||||
|
||||
TextLabel { text: qsTr("IMAP SETTINGS", "title of the portion of the configuration screen that contains IMAP settings"); state: "heading" }
|
||||
Rectangle { width: parent.width; height: Style.info.topMargin; color: "#00000000"}
|
||||
Grid {
|
||||
columns: 2
|
||||
rowSpacing: Style.main.fontSize
|
||||
TextLabel { text: qsTr("Hostname", "in configuration screen, displays the server hostname (127.0.0.1)") + ":"} TextValue { text: root.accData.hostname }
|
||||
TextLabel { text: qsTr("Port", "in configuration screen, displays the server port (ex. 1025)") + ":"} TextValue { text: root.accData.portIMAP }
|
||||
TextLabel { text: qsTr("Username", "in configuration screen, displays the username to use with the desktop client") + ":"} TextValue { text: root.address }
|
||||
TextLabel { text: qsTr("Password", "in configuration screen, displays the Bridge password to use with the desktop client") + ":"} TextValue { text: root.accData.password }
|
||||
TextLabel { text: qsTr("Security", "in configuration screen, displays the IMAP security settings") + ":"} TextValue { text: "STARTTLS" }
|
||||
}
|
||||
Rectangle { width: Style.main.dummy; height: Style.main.fontSize; color: "#00000000"}
|
||||
Rectangle { width: Style.main.dummy; height: Style.info.topMargin; color: "#00000000"}
|
||||
|
||||
TextLabel { text: qsTr("SMTP SETTINGS", "title of the portion of the configuration screen that contains SMTP settings"); state: "heading" }
|
||||
Rectangle { width: Style.main.dummy; height: Style.info.topMargin; color: "#00000000"}
|
||||
Grid {
|
||||
columns: 2
|
||||
rowSpacing: Style.main.fontSize
|
||||
TextLabel { text: qsTr("Hostname", "in configuration screen, displays the server hostname (127.0.0.1)") + ":"} TextValue { text: root.accData.hostname }
|
||||
TextLabel { text: qsTr("Port", "in configuration screen, displays the server port (ex. 1025)") + ":"} TextValue { text: root.accData.portSMTP }
|
||||
TextLabel { text: qsTr("Username", "in configuration screen, displays the username to use with the desktop client") + ":"} TextValue { text: root.address }
|
||||
TextLabel { text: qsTr("Password", "in configuration screen, displays the Bridge password to use with the desktop client") + ":"} TextValue { text: root.accData.password }
|
||||
TextLabel { text: qsTr("Security", "in configuration screen, displays the SMTP security settings") + ":"} TextValue { text: go.isSMTPSTARTTLS() ? "STARTTLS" : "SSL" }
|
||||
}
|
||||
Rectangle { width: Style.main.dummy; height: Style.main.fontSize; color: "#00000000"}
|
||||
Rectangle { width: Style.main.dummy; height: Style.info.topMargin; color: "#00000000"}
|
||||
}
|
||||
|
||||
// apple mail button
|
||||
ButtonRounded{
|
||||
anchors {
|
||||
horizontalCenter: parent.horizontalCenter
|
||||
bottom: parent.bottom
|
||||
bottomMargin: Style.info.topMargin
|
||||
}
|
||||
color_main : Style.main.textBlue
|
||||
isOpaque: false
|
||||
text: qsTr("Configure Apple Mail", "button on configuration screen to automatically configure Apple Mail")
|
||||
height: Style.main.fontSize*2
|
||||
width: 2*parent.width/3
|
||||
onClicked: {
|
||||
go.configureAppleMail(root.indexAccount, root.indexAddress)
|
||||
}
|
||||
visible: go.goos == "darwin"
|
||||
}
|
||||
|
||||
|
||||
function showInfo(iAccount, iAddress) {
|
||||
root.indexAccount = iAccount
|
||||
root.indexAddress = iAddress
|
||||
root.accData = accountsModel.get(iAccount)
|
||||
root.address = accData.aliases.split(";")[iAddress]
|
||||
root.show()
|
||||
root.raise()
|
||||
root.requestActivate()
|
||||
}
|
||||
|
||||
function hide() {
|
||||
root.visible = false
|
||||
}
|
||||
}
|
||||
@ -1,395 +0,0 @@
|
||||
// Copyright (c) 2021 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
// ProtonMail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// ProtonMail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
// This is main window
|
||||
|
||||
import QtQuick 2.8
|
||||
import QtQuick.Window 2.2
|
||||
import QtQuick.Controls 2.1
|
||||
import QtQuick.Layouts 1.3
|
||||
import BridgeUI 1.0
|
||||
import ProtonUI 1.0
|
||||
|
||||
|
||||
// Main Window
|
||||
Window {
|
||||
id: root
|
||||
property alias tabbar : tabbar
|
||||
property alias viewContent : viewContent
|
||||
property alias viewAccount : viewAccount
|
||||
property alias dialogAddUser : dialogAddUser
|
||||
property alias dialogChangePort : dialogChangePort
|
||||
property alias dialogCredits : dialogCredits
|
||||
property alias dialogTlsCert : dialogTlsCert
|
||||
property alias dialogUpdate : dialogUpdate
|
||||
property alias dialogFirstStart : dialogFirstStart
|
||||
property alias dialogGlobal : dialogGlobal
|
||||
property alias dialogConnectionTroubleshoot : dialogConnectionTroubleshoot
|
||||
property alias bubbleNote : bubbleNote
|
||||
property alias addAccountTip : addAccountTip
|
||||
property alias updateState : infoBar.state
|
||||
property alias tlsBarState : tlsBar.state
|
||||
property int heightContent : height-titleBar.height
|
||||
|
||||
// main window appeareance
|
||||
width : Style.main.width
|
||||
height : Style.main.height
|
||||
flags : Qt.Window | Qt.FramelessWindowHint
|
||||
color: go.goos=="windows" ? "black" : "transparent"
|
||||
title: go.programTitle
|
||||
minimumWidth: Style.main.width
|
||||
minimumHeight: Style.main.height
|
||||
maximumWidth: Style.main.width
|
||||
|
||||
property bool isOutdateVersion : root.updateState == "forceUpdate"
|
||||
|
||||
property bool activeContent :
|
||||
!dialogAddUser .visible &&
|
||||
!dialogChangePort .visible &&
|
||||
!dialogCredits .visible &&
|
||||
!dialogTlsCert .visible &&
|
||||
!dialogUpdate .visible &&
|
||||
!dialogFirstStart .visible &&
|
||||
!dialogGlobal .visible &&
|
||||
!bubbleNote .visible
|
||||
|
||||
Accessible.role: Accessible.Grouping
|
||||
Accessible.description: qsTr("Window %1").arg(title)
|
||||
Accessible.name: Accessible.description
|
||||
|
||||
|
||||
Component.onCompleted : {
|
||||
gui.winMain = root
|
||||
console.log("GraphicsInfo of", titleBar,
|
||||
"api" , titleBar.GraphicsInfo.api ,
|
||||
"majorVersion" , titleBar.GraphicsInfo.majorVersion ,
|
||||
"minorVersion" , titleBar.GraphicsInfo.minorVersion ,
|
||||
"profile" , titleBar.GraphicsInfo.profile ,
|
||||
"renderableType" , titleBar.GraphicsInfo.renderableType ,
|
||||
"shaderCompilationType" , titleBar.GraphicsInfo.shaderCompilationType ,
|
||||
"shaderSourceType" , titleBar.GraphicsInfo.shaderSourceType ,
|
||||
"shaderType" , titleBar.GraphicsInfo.shaderType)
|
||||
|
||||
tabbar.focusButton()
|
||||
}
|
||||
|
||||
WindowTitleBar {
|
||||
id: titleBar
|
||||
window: root
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors {
|
||||
top : titleBar.bottom
|
||||
left : parent.left
|
||||
right : parent.right
|
||||
bottom : parent.bottom
|
||||
}
|
||||
color: Style.title.background
|
||||
}
|
||||
|
||||
TLSCertPinIssueBar {
|
||||
id: tlsBar
|
||||
anchors {
|
||||
left : parent.left
|
||||
right : parent.right
|
||||
top : titleBar.bottom
|
||||
leftMargin: Style.main.border
|
||||
rightMargin: Style.main.border
|
||||
}
|
||||
enabled : root.activeContent
|
||||
}
|
||||
|
||||
InformationBar {
|
||||
id: infoBar
|
||||
anchors {
|
||||
left : parent.left
|
||||
right : parent.right
|
||||
top : tlsBar.bottom
|
||||
leftMargin: Style.main.border
|
||||
rightMargin: Style.main.border
|
||||
}
|
||||
enabled : root.activeContent
|
||||
}
|
||||
|
||||
|
||||
TabLabels {
|
||||
id: tabbar
|
||||
currentIndex : 0
|
||||
enabled: root.activeContent
|
||||
anchors {
|
||||
top : infoBar.bottom
|
||||
right : parent.right
|
||||
left : parent.left
|
||||
leftMargin: Style.main.border
|
||||
rightMargin: Style.main.border
|
||||
}
|
||||
model: [
|
||||
{ "title" : qsTr("Accounts" , "title of tab that shows account list" ), "iconText": Style.fa.user_circle_o },
|
||||
{ "title" : qsTr("Settings" , "title of tab that allows user to change settings" ), "iconText": Style.fa.cog },
|
||||
{ "title" : qsTr("Help" , "title of tab that shows the help menu" ), "iconText": Style.fa.life_ring }
|
||||
]
|
||||
}
|
||||
|
||||
// Content of tabs
|
||||
StackLayout {
|
||||
id: viewContent
|
||||
enabled: root.activeContent
|
||||
// dimensions
|
||||
anchors {
|
||||
left : parent.left
|
||||
right : parent.right
|
||||
top : tabbar.bottom
|
||||
bottom : parent.bottom
|
||||
leftMargin: Style.main.border
|
||||
rightMargin: Style.main.border
|
||||
bottomMargin: Style.main.border
|
||||
}
|
||||
// attributes
|
||||
currentIndex : { return root.tabbar.currentIndex}
|
||||
clip : true
|
||||
// content
|
||||
AccountView {
|
||||
id: viewAccount
|
||||
onAddAccount: dialogAddUser.show()
|
||||
model: accountsModel
|
||||
delegate: AccountDelegate {
|
||||
row_width: viewContent.width
|
||||
}
|
||||
}
|
||||
|
||||
SettingsView { id: viewSettings; }
|
||||
HelpView { id: viewHelp; }
|
||||
}
|
||||
|
||||
|
||||
// Floating things
|
||||
|
||||
// Triangle
|
||||
Rectangle {
|
||||
id: tabtriangle
|
||||
visible: false
|
||||
property int margin : Style.main.leftMargin+ Style.tabbar.widthButton/2
|
||||
anchors {
|
||||
top : tabbar.bottom
|
||||
left : tabbar.left
|
||||
leftMargin : tabtriangle.margin - tabtriangle.width/2 + tabbar.currentIndex * tabbar.spacing
|
||||
}
|
||||
width: 2*Style.tabbar.heightTriangle
|
||||
height: Style.tabbar.heightTriangle
|
||||
color: "transparent"
|
||||
Canvas {
|
||||
anchors.fill: parent
|
||||
onPaint: {
|
||||
var ctx = getContext("2d")
|
||||
ctx.fillStyle = Style.tabbar.background
|
||||
ctx.moveTo(0 , 0)
|
||||
ctx.lineTo(width/2, height)
|
||||
ctx.lineTo(width , 0)
|
||||
ctx.closePath()
|
||||
ctx.fill()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Bubble prevent action
|
||||
Rectangle {
|
||||
anchors {
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
top: titleBar.bottom
|
||||
bottom: parent.bottom
|
||||
}
|
||||
visible: bubbleNote.visible
|
||||
color: "#aa222222"
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
}
|
||||
}
|
||||
BubbleNote {
|
||||
id : bubbleNote
|
||||
visible : false
|
||||
Component.onCompleted : {
|
||||
bubbleNote.place(0)
|
||||
}
|
||||
}
|
||||
|
||||
BubbleNote {
|
||||
id:addAccountTip
|
||||
anchors.topMargin: viewAccount.separatorNoAccount - 2*Style.main.fontSize
|
||||
text : qsTr("Click here to start", "on first launch, this is displayed above the Add Account button to tell the user what to do first")
|
||||
state: (go.isFirstStart && viewAccount.numAccounts==0 && root.viewContent.currentIndex==0) ? "Visible" : "Invisible"
|
||||
bubbleColor: Style.main.textBlue
|
||||
|
||||
Component.onCompleted : {
|
||||
addAccountTip.place(-1)
|
||||
}
|
||||
enabled: false
|
||||
|
||||
states: [
|
||||
State {
|
||||
name: "Visible"
|
||||
// hack: opacity 100% makes buttons dialog windows quit wrong color
|
||||
PropertyChanges{target: addAccountTip; opacity: 0.999; visible: true}
|
||||
},
|
||||
State {
|
||||
name: "Invisible"
|
||||
PropertyChanges{target: addAccountTip; opacity: 0.0; visible: false}
|
||||
}
|
||||
]
|
||||
|
||||
transitions: [
|
||||
Transition {
|
||||
from: "Visible"
|
||||
to: "Invisible"
|
||||
|
||||
SequentialAnimation{
|
||||
NumberAnimation {
|
||||
target: addAccountTip
|
||||
property: "opacity"
|
||||
duration: 0
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
NumberAnimation {
|
||||
target: addAccountTip
|
||||
property: "visible"
|
||||
duration: 0
|
||||
}
|
||||
}
|
||||
},
|
||||
Transition {
|
||||
from: "Invisible"
|
||||
to: "Visible"
|
||||
SequentialAnimation{
|
||||
NumberAnimation {
|
||||
target: addAccountTip
|
||||
property: "visible"
|
||||
duration: 300
|
||||
}
|
||||
NumberAnimation {
|
||||
target: addAccountTip
|
||||
property: "opacity"
|
||||
duration: 500
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
// Dialogs
|
||||
DialogFirstStart {
|
||||
id: dialogFirstStart
|
||||
visible: go.isFirstStart && gui.isFirstWindow && !dialogGlobal.visible
|
||||
}
|
||||
|
||||
// Dialogs
|
||||
DialogPortChange {
|
||||
id: dialogChangePort
|
||||
}
|
||||
|
||||
DialogKeychainChange {
|
||||
id: dialogChangeKeychain
|
||||
}
|
||||
|
||||
DialogConnectionTroubleshoot {
|
||||
id: dialogConnectionTroubleshoot
|
||||
}
|
||||
|
||||
DialogAddUser {
|
||||
id: dialogAddUser
|
||||
onCreateAccount: Qt.openUrlExternally("https://protonmail.com/signup")
|
||||
}
|
||||
|
||||
DialogUpdate {
|
||||
id: dialogUpdate
|
||||
forceUpdate: root.isOutdateVersion
|
||||
}
|
||||
|
||||
|
||||
Dialog {
|
||||
id: dialogCredits
|
||||
title: qsTr("Credits", "link to click on to view list of credited libraries")
|
||||
Credits { }
|
||||
}
|
||||
|
||||
DialogTLSCertInfo {
|
||||
id: dialogTlsCert
|
||||
}
|
||||
|
||||
DialogYesNo {
|
||||
id: dialogGlobal
|
||||
question : ""
|
||||
answer : ""
|
||||
z: 100
|
||||
}
|
||||
|
||||
|
||||
// resize
|
||||
MouseArea {
|
||||
property int diff: 0
|
||||
anchors {
|
||||
bottom : parent.bottom
|
||||
left : parent.left
|
||||
right : parent.right
|
||||
}
|
||||
cursorShape: Qt.SizeVerCursor
|
||||
height: Style.main.fontSize
|
||||
onPressed: {
|
||||
var globPos = mapToGlobal(mouse.x, mouse.y)
|
||||
diff = root.height
|
||||
diff -= globPos.y
|
||||
}
|
||||
onMouseYChanged : {
|
||||
var globPos = mapToGlobal(mouse.x, mouse.y)
|
||||
root.height = Math.max(root.minimumHeight, globPos.y + diff)
|
||||
}
|
||||
}
|
||||
|
||||
function showAndRise(){
|
||||
go.loadAccounts()
|
||||
root.show()
|
||||
root.raise()
|
||||
if (!root.active) {
|
||||
root.requestActivate()
|
||||
}
|
||||
}
|
||||
|
||||
// Toggle window
|
||||
function toggle() {
|
||||
go.loadAccounts()
|
||||
if (root.visible) {
|
||||
if (!root.active) {
|
||||
root.raise()
|
||||
root.requestActivate()
|
||||
} else {
|
||||
root.hide()
|
||||
}
|
||||
} else {
|
||||
root.show()
|
||||
root.raise()
|
||||
}
|
||||
}
|
||||
|
||||
onClosing: {
|
||||
close.accepted = false
|
||||
// NOTE: In order to make an initial accounts load
|
||||
root.hide()
|
||||
gui.closeMainWindow()
|
||||
}
|
||||
}
|
||||
@ -1,148 +0,0 @@
|
||||
// Copyright (c) 2021 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
// ProtonMail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// ProtonMail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
// Popup
|
||||
|
||||
import QtQuick 2.8
|
||||
import QtQuick.Window 2.2
|
||||
import BridgeUI 1.0
|
||||
import ProtonUI 1.0
|
||||
|
||||
Window {
|
||||
id:root
|
||||
width : Style.info.width
|
||||
height : Style.info.width/1.5
|
||||
minimumWidth : Style.info.width
|
||||
minimumHeight : Style.info.width/1.5
|
||||
maximumWidth : Style.info.width
|
||||
maximumHeight : Style.info.width/1.5
|
||||
color : Style.main.background
|
||||
flags : Qt.Window | Qt.Popup | Qt.FramelessWindowHint
|
||||
visible : false
|
||||
title : ""
|
||||
x: 10
|
||||
y: 10
|
||||
property string messageID: ""
|
||||
|
||||
// Drag and move
|
||||
MouseArea {
|
||||
property point diff: "0,0"
|
||||
property QtObject window: root
|
||||
|
||||
anchors {
|
||||
fill: parent
|
||||
}
|
||||
|
||||
onPressed: {
|
||||
diff = Qt.point(window.x, window.y)
|
||||
var mousePos = mapToGlobal(mouse.x, mouse.y)
|
||||
diff.x -= mousePos.x
|
||||
diff.y -= mousePos.y
|
||||
}
|
||||
|
||||
onPositionChanged: {
|
||||
var currPos = mapToGlobal(mouse.x, mouse.y)
|
||||
window.x = currPos.x + diff.x
|
||||
window.y = currPos.y + diff.y
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
topPadding: Style.main.fontSize
|
||||
spacing: (root.height - (description.height + cancel.height + countDown.height + Style.main.fontSize))/3
|
||||
width: root.width
|
||||
|
||||
Text {
|
||||
id: description
|
||||
color : Style.main.text
|
||||
font.pointSize : Style.main.fontSize*Style.pt/1.2
|
||||
anchors.horizontalCenter : parent.horizontalCenter
|
||||
horizontalAlignment : Text.AlignHCenter
|
||||
width : root.width - 2*Style.main.leftMargin
|
||||
wrapMode : Text.Wrap
|
||||
textFormat : Text.RichText
|
||||
|
||||
text: qsTr("The message with subject %1 has one or more recipients with no encryption settings. If you do not want to send this email click the cancel button.").arg("<h3>"+root.title+"</h3>")
|
||||
}
|
||||
|
||||
Row {
|
||||
spacing : Style.dialog.spacing
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
ButtonRounded {
|
||||
id: sendAnyway
|
||||
onClicked : root.hide(true)
|
||||
height: Style.main.fontSize*2
|
||||
//width: Style.dialog.widthButton*1.3
|
||||
fa_icon: Style.fa.send
|
||||
text: qsTr("Send now", "Confirmation of sending unencrypted email.")
|
||||
}
|
||||
|
||||
ButtonRounded {
|
||||
id: cancel
|
||||
onClicked : root.hide(false)
|
||||
height: Style.main.fontSize*2
|
||||
//width: Style.dialog.widthButton*1.3
|
||||
fa_icon: Style.fa.times
|
||||
text: qsTr("Cancel", "Cancel the sending of current email")
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
id: countDown
|
||||
color: Style.main.text
|
||||
font.pointSize : Style.main.fontSize*Style.pt/1.2
|
||||
anchors.horizontalCenter : parent.horizontalCenter
|
||||
horizontalAlignment : Text.AlignHCenter
|
||||
width : root.width - 2*Style.main.leftMargin
|
||||
wrapMode : Text.Wrap
|
||||
textFormat : Text.RichText
|
||||
|
||||
text: qsTr("This popup will close after %1 and email will be sent unless you click the cancel button.").arg( "<b>" + timer.secLeft + "s</b>")
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: timer
|
||||
property var secLeft: 0
|
||||
interval: 1000 //ms
|
||||
repeat: true
|
||||
onTriggered: {
|
||||
secLeft--
|
||||
if (secLeft <= 0) {
|
||||
root.hide(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function hide(shouldSend) {
|
||||
root.visible = false
|
||||
timer.stop()
|
||||
go.saveOutgoingNoEncPopupCoord(root.x, root.y)
|
||||
go.shouldSendAnswer(root.messageID, shouldSend)
|
||||
}
|
||||
|
||||
function show(messageID, subject) {
|
||||
root.messageID = messageID
|
||||
root.title = subject
|
||||
root.visible = true
|
||||
timer.secLeft = 10
|
||||
timer.start()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,263 +0,0 @@
|
||||
// Copyright (c) 2021 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
// ProtonMail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// ProtonMail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
// List the settings
|
||||
|
||||
import QtQuick 2.8
|
||||
import BridgeUI 1.0
|
||||
import ProtonUI 1.0
|
||||
import QtQuick.Controls 2.4
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
// must have wrapper
|
||||
ScrollView {
|
||||
id: wrapper
|
||||
anchors.centerIn: parent
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
clip: true
|
||||
background: Rectangle {
|
||||
color: Style.main.background
|
||||
}
|
||||
|
||||
// horizontall scrollbar sometimes showes up when vertical scrollbar coveres content
|
||||
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
|
||||
ScrollBar.vertical.policy: ScrollBar.AsNeeded
|
||||
|
||||
// keeping vertical scrollbar allways visible when needed
|
||||
Connections {
|
||||
target: wrapper.ScrollBar.vertical
|
||||
onSizeChanged: {
|
||||
// ScrollBar.size == 0 at creating so no need to make it active
|
||||
if (wrapper.ScrollBar.vertical.size < 1.0 && wrapper.ScrollBar.vertical.size > 0 && !wrapper.ScrollBar.vertical.active) {
|
||||
wrapper.ScrollBar.vertical.active = true
|
||||
}
|
||||
}
|
||||
onActiveChanged: {
|
||||
wrapper.ScrollBar.vertical.active = true
|
||||
}
|
||||
}
|
||||
|
||||
// content
|
||||
Column {
|
||||
anchors.left : parent.left
|
||||
|
||||
ButtonIconText {
|
||||
id: cacheClear
|
||||
text: qsTr("Clear Cache", "button to clear cache in settings")
|
||||
leftIcon.text : Style.fa.times
|
||||
rightIcon {
|
||||
text : qsTr("Clear", "clickable link next to clear cache button in settings")
|
||||
color: Style.main.text
|
||||
font {
|
||||
family : cacheClear.font.family // use default font, not font-awesome
|
||||
pointSize : Style.settings.fontSize * Style.pt
|
||||
underline : true
|
||||
}
|
||||
}
|
||||
onClicked: {
|
||||
dialogGlobal.state="clearCache"
|
||||
dialogGlobal.show()
|
||||
}
|
||||
}
|
||||
|
||||
ButtonIconText {
|
||||
id: cacheKeychain
|
||||
text: qsTr("Clear Keychain", "button to clear keychain in settings")
|
||||
leftIcon.text : Style.fa.chain_broken
|
||||
rightIcon {
|
||||
text : qsTr("Clear", "clickable link next to clear keychain button in settings")
|
||||
color: Style.main.text
|
||||
font {
|
||||
family : cacheKeychain.font.family // use default font, not font-awesome
|
||||
pointSize : Style.settings.fontSize * Style.pt
|
||||
underline : true
|
||||
}
|
||||
}
|
||||
onClicked: {
|
||||
dialogGlobal.state="clearChain"
|
||||
dialogGlobal.show()
|
||||
}
|
||||
}
|
||||
|
||||
ButtonIconText {
|
||||
id: autoStart
|
||||
text: qsTr("Automatically start Bridge", "label for toggle that activates and disables the automatic start")
|
||||
leftIcon.text : Style.fa.rocket
|
||||
rightIcon {
|
||||
font.pointSize : Style.settings.toggleSize * Style.pt
|
||||
text : go.isAutoStart!=false ? Style.fa.toggle_on : Style.fa.toggle_off
|
||||
color : go.isAutoStart!=false ? Style.main.textBlue : Style.main.textDisabled
|
||||
}
|
||||
Accessible.description: (
|
||||
go.isAutoStart == false ?
|
||||
qsTr("Enable" , "Click to enable the automatic start of Bridge") :
|
||||
qsTr("Disable" , "Click to disable the automatic start of Bridge")
|
||||
) + " " + text
|
||||
onClicked: {
|
||||
go.toggleAutoStart()
|
||||
}
|
||||
}
|
||||
|
||||
ButtonIconText {
|
||||
id: autoUpdates
|
||||
text: qsTr("Keep the application up to date", "label for toggle that activates and disables the automatic updates")
|
||||
leftIcon.text : Style.fa.download
|
||||
rightIcon {
|
||||
font.pointSize : Style.settings.toggleSize * Style.pt
|
||||
text : go.isAutoUpdate!=false ? Style.fa.toggle_on : Style.fa.toggle_off
|
||||
color : go.isAutoUpdate!=false ? Style.main.textBlue : Style.main.textDisabled
|
||||
}
|
||||
Accessible.description: (
|
||||
go.isAutoUpdate == false ?
|
||||
qsTr("Enable" , "Click to enable the automatic update of Bridge") :
|
||||
qsTr("Disable" , "Click to disable the automatic update of Bridge")
|
||||
) + " " + text
|
||||
onClicked: {
|
||||
go.toggleAutoUpdate()
|
||||
}
|
||||
}
|
||||
|
||||
ButtonIconText {
|
||||
id: earlyAccess
|
||||
text: qsTr("Early access", "label for toggle that enables and disables early access")
|
||||
leftIcon.text : Style.fa.star
|
||||
rightIcon {
|
||||
font.pointSize : Style.settings.toggleSize * Style.pt
|
||||
text : go.isEarlyAccess!=false ? Style.fa.toggle_on : Style.fa.toggle_off
|
||||
color : go.isEarlyAccess!=false ? Style.main.textBlue : Style.main.textDisabled
|
||||
}
|
||||
Accessible.description: (
|
||||
go.isEarlyAccess == false ?
|
||||
qsTr("Enable" , "Click to enable early access") :
|
||||
qsTr("Disable" , "Click to disable early access")
|
||||
) + " " + text
|
||||
onClicked: {
|
||||
if (go.isEarlyAccess == true) {
|
||||
dialogGlobal.state="toggleEarlyAccessOff"
|
||||
dialogGlobal.show()
|
||||
} else {
|
||||
dialogGlobal.state="toggleEarlyAccessOn"
|
||||
dialogGlobal.show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ButtonIconText {
|
||||
id: advancedSettings
|
||||
property bool isAdvanced : !go.isDefaultPort
|
||||
text: qsTr("Advanced settings", "button to open the advanced settings list in the settings page")
|
||||
leftIcon.text : Style.fa.cogs
|
||||
rightIcon {
|
||||
font.pointSize : Style.settings.toggleSize * Style.pt
|
||||
text : isAdvanced!=0 ? Style.fa.chevron_circle_up : Style.fa.chevron_circle_right
|
||||
color : isAdvanced!=0 ? Style.main.textDisabled : Style.main.textBlue
|
||||
}
|
||||
|
||||
Accessible.description: (
|
||||
isAdvanced ?
|
||||
qsTr("Hide", "Click to hide the advance settings") :
|
||||
qsTr("Show", "Click to show the advance settings")
|
||||
) + " " + text
|
||||
onClicked: {
|
||||
isAdvanced = !isAdvanced
|
||||
}
|
||||
}
|
||||
|
||||
ButtonIconText {
|
||||
id: changePort
|
||||
visible: advancedSettings.isAdvanced
|
||||
text: qsTr("Change IMAP & SMTP settings", "button to change IMAP and SMTP ports in settings")
|
||||
leftIcon.text : Style.fa.plug
|
||||
rightIcon {
|
||||
text : qsTr("Change", "clickable link next to change ports button in settings")
|
||||
color: Style.main.text
|
||||
font {
|
||||
family : changePort.font.family // use default font, not font-awesome
|
||||
pointSize : Style.settings.fontSize * Style.pt
|
||||
underline : true
|
||||
}
|
||||
}
|
||||
onClicked: {
|
||||
dialogChangePort.show()
|
||||
}
|
||||
}
|
||||
|
||||
ButtonIconText {
|
||||
id: reportNoEnc
|
||||
text: qsTr("Notification of outgoing email without encryption", "Button to set whether to report or send an email without encryption")
|
||||
visible: advancedSettings.isAdvanced
|
||||
leftIcon.text : Style.fa.ban
|
||||
rightIcon {
|
||||
font.pointSize : Style.settings.toggleSize * Style.pt
|
||||
text : go.isReportingOutgoingNoEnc ? Style.fa.toggle_on : Style.fa.toggle_off
|
||||
color : go.isReportingOutgoingNoEnc ? Style.main.textBlue : Style.main.textDisabled
|
||||
}
|
||||
Accessible.description: (
|
||||
go.isReportingOutgoingNoEnc == 0 ?
|
||||
qsTr("Enable" , "Click to report an email without encryption") :
|
||||
qsTr("Disable" , "Click to send without asking an email without encryption")
|
||||
) + " " + text
|
||||
onClicked: {
|
||||
go.toggleIsReportingOutgoingNoEnc()
|
||||
}
|
||||
}
|
||||
|
||||
ButtonIconText {
|
||||
id: allowProxy
|
||||
visible: advancedSettings.isAdvanced
|
||||
text: qsTr("Allow alternative routing", "label for toggle that allows and disallows using a proxy")
|
||||
leftIcon.text : Style.fa.rocket
|
||||
rightIcon {
|
||||
font.pointSize : Style.settings.toggleSize * Style.pt
|
||||
text : go.isProxyAllowed!=false ? Style.fa.toggle_on : Style.fa.toggle_off
|
||||
color : go.isProxyAllowed!=false ? Style.main.textBlue : Style.main.textDisabled
|
||||
}
|
||||
Accessible.description: (
|
||||
go.isProxyAllowed == false ?
|
||||
qsTr("Enable" , "Click to allow alternative routing") :
|
||||
qsTr("Disable" , "Click to disallow alternative routing")
|
||||
) + " " + text
|
||||
onClicked: {
|
||||
dialogGlobal.state="toggleAllowProxy"
|
||||
dialogGlobal.show()
|
||||
}
|
||||
}
|
||||
|
||||
ButtonIconText {
|
||||
id: changeKeychain
|
||||
visible: advancedSettings.isAdvanced && (go.availableKeychain.length > 1)
|
||||
text: qsTr("Change keychain", "button to open dialog with default keychain selection")
|
||||
leftIcon.text : Style.fa.key
|
||||
rightIcon {
|
||||
text : qsTr("Change", "clickable link next to change keychain button in settings")
|
||||
color: Style.main.text
|
||||
font {
|
||||
family : changeKeychain.font.family // use default font, not font-awesome
|
||||
pointSize : Style.settings.fontSize * Style.pt
|
||||
underline : true
|
||||
}
|
||||
}
|
||||
onClicked: {
|
||||
dialogChangeKeychain.show()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,15 +0,0 @@
|
||||
module BridgeUI
|
||||
AccountDelegate 1.0 AccountDelegate.qml
|
||||
Credits 1.0 Credits.qml
|
||||
DialogFirstStart 1.0 DialogFirstStart.qml
|
||||
DialogKeychainChange 1.0 DialogKeychainChange.qml
|
||||
DialogPortChange 1.0 DialogPortChange.qml
|
||||
DialogYesNo 1.0 DialogYesNo.qml
|
||||
DialogTLSCertInfo 1.0 DialogTLSCertInfo.qml
|
||||
HelpView 1.0 HelpView.qml
|
||||
InfoWindow 1.0 InfoWindow.qml
|
||||
MainWindow 1.0 MainWindow.qml
|
||||
ManualWindow 1.0 ManualWindow.qml
|
||||
OutgoingNoEncPopup 1.0 OutgoingNoEncPopup.qml
|
||||
SettingsView 1.0 SettingsView.qml
|
||||
StatusFooter 1.0 StatusFooter.qml
|
||||
910
internal/frontend/qml/Bridge_test.qml
Normal file
910
internal/frontend/qml/Bridge_test.qml
Normal file
@ -0,0 +1,910 @@
|
||||
// Copyright (c) 2022 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
// ProtonMail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// ProtonMail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import QtQml 2.12
|
||||
import QtQuick 2.13
|
||||
import QtQuick.Window 2.13
|
||||
import QtQuick.Layouts 1.12
|
||||
import QtQuick.Controls 2.13
|
||||
|
||||
import QtQml.Models 2.12
|
||||
|
||||
import Qt.labs.platform 1.1
|
||||
|
||||
import Proton 4.0
|
||||
|
||||
import "./BridgeTest"
|
||||
import BridgePreview 1.0
|
||||
|
||||
import Notifications 1.0
|
||||
|
||||
Window {
|
||||
id: root
|
||||
|
||||
x: 10
|
||||
y: 10
|
||||
width: 800
|
||||
height: 800
|
||||
|
||||
property ColorScheme colorScheme: ProtonStyle.darkStyle
|
||||
|
||||
flags : Qt.Window | Qt.Dialog
|
||||
visible : true
|
||||
title : "Bridge Test GUI"
|
||||
|
||||
// This is needed because on MacOS if first window shown is not transparent -
|
||||
// all other windows of application will not have transparent background (black
|
||||
// instead of transparency). In our case that mean that if BridgeTest will be
|
||||
// shown before StatusWindow - StatusWindow will not have transparent corners.
|
||||
color: "transparent"
|
||||
|
||||
function getCursorPos() {
|
||||
return BridgePreview.getCursorPos()
|
||||
}
|
||||
function quit() {
|
||||
if (bridge !== undefined && bridge !== null) {
|
||||
bridge.destroy()
|
||||
}
|
||||
}
|
||||
|
||||
function guiReady() {
|
||||
console.log("Gui Ready")
|
||||
}
|
||||
|
||||
function _log(msg, color) {
|
||||
logTextArea.text += "<p style='color: " + color + ";'>" + msg + "</p>"
|
||||
logTextArea.text += "\n"
|
||||
}
|
||||
|
||||
function log(msg) {
|
||||
console.log(msg)
|
||||
_log(msg, root.colorScheme.signal_info)
|
||||
}
|
||||
|
||||
function error(msg) {
|
||||
console.error(msg)
|
||||
_log(msg, root.colorScheme.signal_danger)
|
||||
}
|
||||
|
||||
// No user object should be put in this list until a successful login
|
||||
property var users: UserModel {
|
||||
id: _users
|
||||
|
||||
onRowsInserted: {
|
||||
for (var i = first; i <= last; i++) {
|
||||
_usersTest.insert(i + 1, { object: get(i) } )
|
||||
}
|
||||
}
|
||||
|
||||
onRowsRemoved: {
|
||||
_usersTest.remove(first + 1, first - last + 1)
|
||||
}
|
||||
|
||||
onRowsMoved: {
|
||||
_usersTest.move(start + 1, row + 1, end - start + 1)
|
||||
}
|
||||
|
||||
onDataChanged: {
|
||||
for (var i = topLeft.row; i <= bottomRight.row; i++) {
|
||||
_usersTest.set(i + 1, { object: get(i) } )
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// this list is used on test gui: it contains same users list as users above + fake user to represent login request of new user on pos 0
|
||||
property var usersTest: UserModel {
|
||||
id: _usersTest
|
||||
}
|
||||
|
||||
property var userComponent: Component {
|
||||
id: _userComponent
|
||||
|
||||
QtObject {
|
||||
property string username: ""
|
||||
property bool loggedIn: false
|
||||
property bool splitMode: false
|
||||
|
||||
property bool setupGuideSeen: true
|
||||
|
||||
property var usedBytes: 5350*1024*1024
|
||||
property var totalBytes: 20*1024*1024*1024
|
||||
property string avatarText: "jd"
|
||||
|
||||
property string password: "SMj975NnEYYsqu55GGmlpv"
|
||||
property var addresses: [
|
||||
"jaanedoe@protonmail.com",
|
||||
"jane@pm.me",
|
||||
"jdoe@pm.me"
|
||||
]
|
||||
|
||||
signal loginUsernamePasswordError()
|
||||
signal loginFreeUserError()
|
||||
signal loginConnectionError()
|
||||
signal login2FARequested()
|
||||
signal login2FAError()
|
||||
signal login2FAErrorAbort()
|
||||
signal login2PasswordRequested()
|
||||
signal login2PasswordError()
|
||||
signal login2PasswordErrorAbort()
|
||||
|
||||
// Test purpose only:
|
||||
property bool isFakeUser: this === root.loginUser
|
||||
|
||||
function userSignal(msg) {
|
||||
if (isFakeUser) {
|
||||
return
|
||||
}
|
||||
|
||||
root.log("<- User (" + username + "): " + msg)
|
||||
}
|
||||
|
||||
function toggleSplitMode(makeActive) {
|
||||
userSignal("toggle split mode "+makeActive)
|
||||
}
|
||||
signal toggleSplitModeFinished()
|
||||
|
||||
function configureAppleMail(address){
|
||||
userSignal("confugure apple mail "+address)
|
||||
}
|
||||
|
||||
function logout(){
|
||||
userSignal("logout")
|
||||
loggedIn = false
|
||||
}
|
||||
function remove(){
|
||||
console.log("remove this", users.count)
|
||||
for (var i=0; i<users.count; i++) {
|
||||
if (users.get(i) === this) {
|
||||
users.remove(i,1)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
onLoginUsernamePasswordError: {
|
||||
userSignal("loginUsernamePasswordError")
|
||||
}
|
||||
onLoginFreeUserError: {
|
||||
userSignal("loginFreeUserError")
|
||||
}
|
||||
onLoginConnectionError: {
|
||||
userSignal("loginConnectionError")
|
||||
}
|
||||
onLogin2FARequested: {
|
||||
userSignal("login2FARequested")
|
||||
}
|
||||
onLogin2FAError: {
|
||||
userSignal("login2FAError")
|
||||
}
|
||||
onLogin2FAErrorAbort: {
|
||||
userSignal("login2FAErrorAbort")
|
||||
}
|
||||
onLogin2PasswordRequested: {
|
||||
userSignal("login2PasswordRequested")
|
||||
}
|
||||
onLogin2PasswordError: {
|
||||
userSignal("login2PasswordError")
|
||||
}
|
||||
onLogin2PasswordErrorAbort: {
|
||||
userSignal("login2PasswordErrorAbort")
|
||||
}
|
||||
|
||||
function resetLoginRequests() {
|
||||
isLoginRequested = false
|
||||
isLogin2FARequested = false
|
||||
isLogin2FAProvided = false
|
||||
isLogin2PasswordRequested = false
|
||||
isLogin2PasswordProvided = false
|
||||
}
|
||||
|
||||
property bool isLoginRequested: false
|
||||
|
||||
property bool isLogin2FARequested: false
|
||||
property bool isLogin2FAProvided: false
|
||||
|
||||
property bool isLogin2PasswordRequested: false
|
||||
property bool isLogin2PasswordProvided: false
|
||||
}
|
||||
}
|
||||
|
||||
// this it fake user used only for representing first login request
|
||||
property var loginUser
|
||||
Component.onCompleted: {
|
||||
var newLoginUser = _userComponent.createObject()
|
||||
root.loginUser = newLoginUser
|
||||
root.loginUser.setupGuideSeen = false
|
||||
_usersTest.append({object: newLoginUser})
|
||||
|
||||
newLoginUser.loginUsernamePasswordError.connect(root.loginUsernamePasswordError)
|
||||
newLoginUser.loginFreeUserError.connect(root.loginFreeUserError)
|
||||
newLoginUser.loginConnectionError.connect(root.loginConnectionError)
|
||||
newLoginUser.login2FARequested.connect(root.login2FARequested)
|
||||
newLoginUser.login2FAError.connect(root.login2FAError)
|
||||
newLoginUser.login2FAErrorAbort.connect(root.login2FAErrorAbort)
|
||||
newLoginUser.login2PasswordRequested.connect(root.login2PasswordRequested)
|
||||
newLoginUser.login2PasswordError.connect(root.login2PasswordError)
|
||||
newLoginUser.login2PasswordErrorAbort.connect(root.login2PasswordErrorAbort)
|
||||
|
||||
|
||||
// add one user on start
|
||||
var hasUserOnStart = true
|
||||
if (hasUserOnStart) {
|
||||
var newUserObject = root.userComponent.createObject(root)
|
||||
newUserObject.username = "LerooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooyJenkins@protonmail.com"
|
||||
newUserObject.loggedIn = true
|
||||
newUserObject.setupGuideSeen = true
|
||||
root.users.append( { object: newUserObject } )
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TabBar {
|
||||
id: tabBar
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
|
||||
TabButton {
|
||||
text: "Global settings"
|
||||
}
|
||||
|
||||
TabButton {
|
||||
text: "User control"
|
||||
}
|
||||
|
||||
TabButton {
|
||||
text: "Notifications"
|
||||
}
|
||||
|
||||
TabButton {
|
||||
text: "Log"
|
||||
}
|
||||
|
||||
TabButton {
|
||||
text: "Settings signals"
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
color: root.colorScheme.background_norm
|
||||
|
||||
anchors.top: tabBar.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
|
||||
implicitHeight: children[0].contentHeight + children[0].anchors.topMargin + children[0].anchors.bottomMargin
|
||||
implicitWidth: children[0].contentWidth + children[0].anchors.leftMargin + children[0].anchors.rightMargin
|
||||
|
||||
StackLayout {
|
||||
anchors.fill: parent
|
||||
currentIndex: tabBar.currentIndex
|
||||
anchors.margins: 10
|
||||
|
||||
RowLayout {
|
||||
id: globalTab
|
||||
spacing : 5
|
||||
|
||||
ColumnLayout {
|
||||
spacing : 5
|
||||
|
||||
Label {
|
||||
colorScheme: root.colorScheme
|
||||
text: "Global settings"
|
||||
}
|
||||
|
||||
ButtonGroup {
|
||||
id: styleRadioGroup
|
||||
}
|
||||
|
||||
RadioButton {
|
||||
colorScheme: root.colorScheme
|
||||
Layout.fillWidth: true
|
||||
|
||||
text: "Light UI"
|
||||
checked: ProtonStyle.currentStyle === ProtonStyle.lightStyle
|
||||
ButtonGroup.group: styleRadioGroup
|
||||
|
||||
onCheckedChanged: {
|
||||
if (checked && ProtonStyle.currentStyle !== ProtonStyle.lightStyle) {
|
||||
root.colorSchemeName = "light"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RadioButton {
|
||||
colorScheme: root.colorScheme
|
||||
Layout.fillWidth: true
|
||||
|
||||
text: "Dark UI"
|
||||
checked: ProtonStyle.currentStyle === ProtonStyle.darkStyle
|
||||
ButtonGroup.group: styleRadioGroup
|
||||
|
||||
onCheckedChanged: {
|
||||
if (checked && ProtonStyle.currentStyle !== ProtonStyle.darkStyle) {
|
||||
root.colorSchemeName = "dark"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CheckBox {
|
||||
id: showOnStartupCheckbox
|
||||
colorScheme: root.colorScheme
|
||||
text: "Show on startup"
|
||||
checked: root.showOnStartup
|
||||
onCheckedChanged: {
|
||||
root.showOnStartup = checked
|
||||
}
|
||||
}
|
||||
|
||||
CheckBox {
|
||||
id: showSplashScreen
|
||||
colorScheme: root.colorScheme
|
||||
text: "Show splash screen"
|
||||
checked: root.showSplashScreen
|
||||
onCheckedChanged: {
|
||||
root.showSplashScreen = checked
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
colorScheme: root.colorScheme
|
||||
//Layout.fillWidth: true
|
||||
|
||||
text: "Open Bridge"
|
||||
enabled: bridge === undefined || bridge === null
|
||||
onClicked: {
|
||||
bridge = bridgeComponent.createObject()
|
||||
var showSetupGuide = false
|
||||
if (showSetupGuide) {
|
||||
var newUserObject = root.userComponent.createObject(root)
|
||||
newUserObject.username = "LerooooyJenkins@protonmail.com"
|
||||
newUserObject.loggedIn = true
|
||||
newUserObject.setupGuideSeen = false
|
||||
root.users.append( { object: newUserObject } )
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
colorScheme: root.colorScheme
|
||||
//Layout.fillWidth: true
|
||||
|
||||
text: "Close Bridge"
|
||||
enabled: bridge !== undefined && bridge !== null
|
||||
onClicked: {
|
||||
bridge.destroy()
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillHeight: true
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing : 5
|
||||
|
||||
Label {
|
||||
colorScheme: root.colorScheme
|
||||
text: "Notifications"
|
||||
}
|
||||
|
||||
Button {
|
||||
colorScheme: root.colorScheme
|
||||
text: "Notify: danger"
|
||||
enabled: bridge !== undefined && bridge !== null
|
||||
onClicked: {
|
||||
bridge.mainWindow.notifyOnlyPaidUsers()
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
colorScheme: root.colorScheme
|
||||
text: "Notify: warning"
|
||||
enabled: bridge !== undefined && bridge !== null
|
||||
onClicked: {
|
||||
bridge.mainWindow.notifyUpdateManually()
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
colorScheme: root.colorScheme
|
||||
text: "Notify: success"
|
||||
enabled: bridge !== undefined && bridge !== null
|
||||
onClicked: {
|
||||
bridge.mainWindow.notifyUserAdded()
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillHeight: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: usersTab
|
||||
UserList {
|
||||
id: usersListView
|
||||
Layout.fillHeight: true
|
||||
colorScheme: root.colorScheme
|
||||
backend: root
|
||||
}
|
||||
|
||||
UserControl {
|
||||
colorScheme: root.colorScheme
|
||||
backend: root
|
||||
user: ((root.usersTest.count > usersListView.currentIndex) && usersListView.currentIndex != -1) ? root.usersTest.get(usersListView.currentIndex) : undefined
|
||||
userIndex: usersListView.currentIndex - 1 // -1 because 0 index is fake user
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: notificationsTab
|
||||
spacing: 5
|
||||
|
||||
ColumnLayout {
|
||||
spacing: 5
|
||||
|
||||
Switch {
|
||||
text: "Internet connection"
|
||||
colorScheme: root.colorScheme
|
||||
checked: true
|
||||
onCheckedChanged: {
|
||||
checked ? root.internetOn() : root.internetOff()
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
text: "Update manual ready"
|
||||
colorScheme: root.colorScheme
|
||||
onClicked: {
|
||||
root.updateManualReady("3.14.1592")
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
text: "Update manual done"
|
||||
colorScheme: root.colorScheme
|
||||
onClicked: {
|
||||
root.updateManualRestartNeeded()
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
text: "Update manual error"
|
||||
colorScheme: root.colorScheme
|
||||
onClicked: {
|
||||
root.updateManualError()
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
text: "Update force"
|
||||
colorScheme: root.colorScheme
|
||||
onClicked: {
|
||||
root.updateForce("3.14.1592")
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
text: "Update force error"
|
||||
colorScheme: root.colorScheme
|
||||
onClicked: {
|
||||
root.updateForceError()
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
text: "Update silent done"
|
||||
colorScheme: root.colorScheme
|
||||
onClicked: {
|
||||
root.updateSilentRestartNeeded()
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
text: "Update silent error"
|
||||
colorScheme: root.colorScheme
|
||||
onClicked: {
|
||||
root.updateSilentError()
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
text: "Update is latest version"
|
||||
colorScheme: root.colorScheme
|
||||
onClicked: {
|
||||
root.updateIsLatestVersion()
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
text: "Bug report send OK"
|
||||
colorScheme: root.colorScheme
|
||||
onClicked: {
|
||||
root.reportBugFinished()
|
||||
root.bugReportSendSuccess()
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
text: "Bug report send error"
|
||||
colorScheme: root.colorScheme
|
||||
onClicked: {
|
||||
root.reportBugFinished()
|
||||
root.bugReportSendError()
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
text: "Cache anavailable"
|
||||
colorScheme: root.colorScheme
|
||||
onClicked: {
|
||||
root.cacheUnavailable()
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
text: "Cache can't move"
|
||||
colorScheme: root.colorScheme
|
||||
onClicked: {
|
||||
root.cacheCantMove()
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
text: "Cache location change success"
|
||||
onClicked: {
|
||||
root.cacheLocationChangeSuccess()
|
||||
}
|
||||
colorScheme: root.colorScheme
|
||||
}
|
||||
|
||||
Button {
|
||||
text: "Disk full"
|
||||
colorScheme: root.colorScheme
|
||||
onClicked: {
|
||||
root.diskFull()
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
text: "No keychain"
|
||||
colorScheme: root.colorScheme
|
||||
onClicked: {
|
||||
root.hasNoKeychain()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TextArea {
|
||||
id: logTextArea
|
||||
colorScheme: root.colorScheme
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
|
||||
Layout.preferredWidth: 400
|
||||
Layout.preferredHeight: 200
|
||||
|
||||
textFormat: TextEdit.RichText
|
||||
//readOnly: true
|
||||
}
|
||||
|
||||
ScrollView {
|
||||
id: settingsTab
|
||||
ColumnLayout {
|
||||
RowLayout {
|
||||
Label {colorScheme: root.colorScheme; text: "Automatic updates:"}
|
||||
Toggle {colorScheme: root.colorScheme; checked: root.isAutomaticUpdateOn; onClicked: root.isAutomaticUpdateOn = !root.isAutomaticUpdateOn}
|
||||
}
|
||||
RowLayout {
|
||||
Label {colorScheme: root.colorScheme; text: "Autostart:"}
|
||||
Toggle {colorScheme: root.colorScheme; checked: root.isAutostartOn; onClicked: root.isAutostartOn = !root.isAutostartOn}
|
||||
Button {colorScheme: root.colorScheme; text: "Toggle finished"; onClicked: root.toggleAutostartFinished()}
|
||||
}
|
||||
RowLayout {
|
||||
Label {colorScheme: root.colorScheme; text: "Beta:"}
|
||||
Toggle {colorScheme: root.colorScheme; checked: root.isBetaEnabled; onClicked: root.isBetaEnabled = !root.isBetaEnabled}
|
||||
}
|
||||
RowLayout {
|
||||
Label {colorScheme: root.colorScheme; text: "DoH:"}
|
||||
Toggle {colorScheme: root.colorScheme; checked: root.isDoHEnabled; onClicked: root.isDoHEnabled = !root.isDoHEnabled}
|
||||
}
|
||||
RowLayout {
|
||||
Label {colorScheme: root.colorScheme; text: "Ports:"}
|
||||
TextField {
|
||||
colorScheme:root.colorScheme
|
||||
label: "IMAP"
|
||||
text: root.portIMAP
|
||||
onEditingFinished: root.portIMAP = this.text*1
|
||||
validator: IntValidator {bottom: 1; top: 65536}
|
||||
}
|
||||
TextField {
|
||||
colorScheme:root.colorScheme
|
||||
label: "SMTP"
|
||||
text: root.portSMTP
|
||||
onEditingFinished: root.portSMTP = this.text*1
|
||||
validator: IntValidator {bottom: 1; top: 65536}
|
||||
}
|
||||
Button {colorScheme: root.colorScheme; text: "Change finished"; onClicked: root.changePortFinished()}
|
||||
}
|
||||
RowLayout {
|
||||
Label {colorScheme: root.colorScheme; text: "SMTP using SSL:"}
|
||||
Toggle {colorScheme: root.colorScheme; checked: root.useSSLforSMTP; onClicked: root.useSSLforSMTP = !root.useSSLforSMTP}
|
||||
}
|
||||
RowLayout {
|
||||
Label {colorScheme: root.colorScheme; text: "Local cache:"}
|
||||
Toggle {colorScheme: root.colorScheme; checked: root.isDiskCacheEnabled; onClicked: root.isDiskCacheEnabled = !root.isDiskCacheEnabled}
|
||||
TextField {
|
||||
colorScheme:root.colorScheme
|
||||
label: "Path"
|
||||
text: root.diskCachePath.toString().replace("file://", "")
|
||||
implicitWidth: 160
|
||||
onEditingFinished: {
|
||||
root.diskCachePath = Qt.resolvedUrl("file://"+text)
|
||||
}
|
||||
}
|
||||
Button {colorScheme: root.colorScheme; text: "Change finished"; onClicked: root.changeLocalCacheFinished()}
|
||||
}
|
||||
RowLayout {
|
||||
Label {colorScheme: root.colorScheme; text: "Reset:"}
|
||||
Button {colorScheme: root.colorScheme; text: "Finished"; onClicked: root.resetFinished()}
|
||||
}
|
||||
RowLayout {
|
||||
Label {colorScheme: root.colorScheme; text: "Check update:"}
|
||||
Button {colorScheme: root.colorScheme; text: "Finished"; onClicked: root.checkUpdatesFinished()}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
property Bridge bridge
|
||||
|
||||
property string goos: "darwin"
|
||||
|
||||
property bool showOnStartup: true // this actually needs to be false, but since we use Bridge_test for testing purpose - lets default this to true just for convenience
|
||||
property bool dockIconVisible: false
|
||||
|
||||
// this signals are used only when trying to login with new user (i.e. not in users model)
|
||||
signal loginUsernamePasswordError(string errorMsg)
|
||||
signal loginFreeUserError()
|
||||
signal loginConnectionError(string errorMsg)
|
||||
signal login2FARequested(string username)
|
||||
signal login2FAError(string errorMsg)
|
||||
signal login2FAErrorAbort(string errorMsg)
|
||||
signal login2PasswordRequested()
|
||||
signal login2PasswordError(string errorMsg)
|
||||
signal login2PasswordErrorAbort(string errorMsg)
|
||||
signal loginFinished(int index)
|
||||
signal loginAlreadyLoggedIn(int index)
|
||||
|
||||
signal internetOff()
|
||||
signal internetOn()
|
||||
|
||||
signal updateManualReady(var version)
|
||||
signal updateManualRestartNeeded()
|
||||
signal updateManualError()
|
||||
signal updateForce(var version)
|
||||
signal updateForceError()
|
||||
signal updateSilentRestartNeeded()
|
||||
signal updateSilentError()
|
||||
signal updateIsLatestVersion()
|
||||
function checkUpdates(){
|
||||
console.log("check updates")
|
||||
}
|
||||
signal checkUpdatesFinished()
|
||||
|
||||
|
||||
property bool isDiskCacheEnabled: true
|
||||
// Qt.resolvedUrl("file:///C:/Users/user/AppData/Roaming/protonmail/bridge/cache/c11/messages")
|
||||
property url diskCachePath: StandardPaths.standardLocations(StandardPaths.HomeLocation)[0]
|
||||
signal cacheUnavailable()
|
||||
signal cacheCantMove()
|
||||
signal cacheLocationChangeSuccess()
|
||||
signal diskFull()
|
||||
function changeLocalCache(enableDiskCache, diskCachePath) {
|
||||
console.debug("-> disk cache", enableDiskCache, diskCachePath)
|
||||
}
|
||||
signal changeLocalCacheFinished()
|
||||
|
||||
|
||||
// Settings
|
||||
property bool isAutomaticUpdateOn : true
|
||||
function toggleAutomaticUpdate(makeItActive) {
|
||||
console.debug("-> silent updates", makeItActive, root.isAutomaticUpdateOn)
|
||||
root.isAutomaticUpdateOn = makeItActive
|
||||
}
|
||||
|
||||
property bool isAutostartOn : true // Example of settings with loading state
|
||||
function toggleAutostart(makeItActive) {
|
||||
console.debug("-> autostart", makeItActive, root.isAutostartOn)
|
||||
}
|
||||
signal toggleAutostartFinished()
|
||||
|
||||
property bool isBetaEnabled : false
|
||||
function toggleBeta(makeItActive){
|
||||
console.debug("-> beta", makeItActive, root.isBetaEnabled)
|
||||
root.isBetaEnabled = makeItActive
|
||||
}
|
||||
|
||||
property bool isDoHEnabled : true
|
||||
function toggleDoH(makeItActive){
|
||||
console.debug("-> DoH", makeItActive, root.isDoHEnabled)
|
||||
root.isDoHEnabled = makeItActive
|
||||
}
|
||||
|
||||
property bool useSSLforSMTP: false
|
||||
function toggleUseSSLforSMTP(makeItActive){
|
||||
console.debug("-> SMTP SSL", makeItActive, root.useSSLforSMTP)
|
||||
}
|
||||
signal toggleUseSSLFinished()
|
||||
|
||||
property string hostname: "127.0.0.1"
|
||||
property int portIMAP: 1143
|
||||
property int portSMTP: 1025
|
||||
function changePorts(imapPort, smtpPort){
|
||||
console.debug("-> ports", imapPort, smtpPort)
|
||||
}
|
||||
function isPortFree(port){
|
||||
if (port == portIMAP) return false
|
||||
if (port == portSMTP) return false
|
||||
if (port == 12345) return false
|
||||
return true
|
||||
}
|
||||
signal changePortFinished()
|
||||
signal portIssueIMAP()
|
||||
signal portIssueSMTP()
|
||||
|
||||
function triggerReset() {
|
||||
console.debug("-> trigger reset")
|
||||
}
|
||||
signal resetFinished()
|
||||
|
||||
property string version: "2.0.X-BridePreview"
|
||||
property url logsPath: StandardPaths.standardLocations(StandardPaths.HomeLocation)[0]
|
||||
property url licensePath: StandardPaths.standardLocations(StandardPaths.HomeLocation)[0]
|
||||
property url releaseNotesLink: Qt.resolvedUrl("https://protonmail.com/download/bridge/early_releases.html")
|
||||
property url landingPageLink: Qt.resolvedUrl("https://protonmail.com/bridge")
|
||||
|
||||
property string colorSchemeName: "light"
|
||||
function changeColorScheme(newScheme){
|
||||
root.colorSchemeName = newScheme
|
||||
}
|
||||
|
||||
|
||||
property string currentEmailClient: "" // "Apple Mail 14.0"
|
||||
function updateCurrentMailClient(){
|
||||
currentEmailClient = "Apple Mail 14.0"
|
||||
}
|
||||
|
||||
function reportBug(description,address,emailClient,includeLogs){
|
||||
console.log("report bug")
|
||||
console.log(" description",description)
|
||||
console.log(" address",address)
|
||||
console.log(" emailClient",emailClient)
|
||||
console.log(" includeLogs",includeLogs)
|
||||
}
|
||||
signal reportBugFinished()
|
||||
signal bugReportSendSuccess()
|
||||
signal bugReportSendError()
|
||||
|
||||
property var availableKeychain: ["gnome-keyring", "pass", "macos-keychain", "windows-credentials"]
|
||||
property string currentKeychain: availableKeychain[0]
|
||||
function changeKeychain(wantedKeychain){
|
||||
console.log("Changing keychain from", root.currentKeychain, "to", wantedKeychain)
|
||||
root.currentKeychain = wantedKeychain
|
||||
root.changeKeychainFinished()
|
||||
}
|
||||
signal changeKeychainFinished()
|
||||
signal hasNoKeychain()
|
||||
|
||||
signal noActiveKeyForRecipient(string email)
|
||||
signal showMainWindow()
|
||||
|
||||
signal addressChanged(string address)
|
||||
signal addressChangedLogout(string address)
|
||||
signal userDisconnected(string username)
|
||||
signal apiCertIssue()
|
||||
|
||||
property bool showSplashScreen: false
|
||||
|
||||
|
||||
function login(username, password) {
|
||||
root.log("-> login(" + username + ", " + password + ")")
|
||||
|
||||
loginUser.username = username
|
||||
loginUser.isLoginRequested = true
|
||||
}
|
||||
|
||||
function login2FA(username, code) {
|
||||
root.log("-> login2FA(" + username + ", " + code + ")")
|
||||
|
||||
loginUser.isLogin2FAProvided = true
|
||||
}
|
||||
|
||||
function login2Password(username, password) {
|
||||
root.log("-> login2FA(" + username + ", " + password + ")")
|
||||
|
||||
loginUser.isLogin2PasswordProvided = true
|
||||
}
|
||||
|
||||
function loginAbort(username) {
|
||||
root.log("-> loginAbort(" + username + ")")
|
||||
|
||||
loginUser.resetLoginRequests()
|
||||
}
|
||||
|
||||
|
||||
onLoginUsernamePasswordError: {
|
||||
console.debug("<- loginUsernamePasswordError")
|
||||
}
|
||||
onLoginFreeUserError: {
|
||||
console.debug("<- loginFreeUserError")
|
||||
}
|
||||
onLoginConnectionError: {
|
||||
console.debug("<- loginConnectionError")
|
||||
}
|
||||
onLogin2FARequested: {
|
||||
console.debug("<- login2FARequested", username)
|
||||
}
|
||||
onLogin2FAError: {
|
||||
console.debug("<- login2FAError")
|
||||
}
|
||||
onLogin2FAErrorAbort: {
|
||||
console.debug("<- login2FAErrorAbort")
|
||||
}
|
||||
onLogin2PasswordRequested: {
|
||||
console.debug("<- login2PasswordRequested")
|
||||
}
|
||||
onLogin2PasswordError: {
|
||||
console.debug("<- login2PasswordError")
|
||||
}
|
||||
onLogin2PasswordErrorAbort: {
|
||||
console.debug("<- login2PasswordErrorAbort")
|
||||
}
|
||||
onLoginFinished: {
|
||||
console.debug("<- loginFinished", index)
|
||||
}
|
||||
onLoginAlreadyLoggedIn: {
|
||||
console.debug("<- loginAlreadyLoggedIn", index)
|
||||
}
|
||||
|
||||
onInternetOff: {
|
||||
console.debug("<- internetOff")
|
||||
}
|
||||
onInternetOn: {
|
||||
console.debug("<- internetOn")
|
||||
}
|
||||
|
||||
Component {
|
||||
id: bridgeComponent
|
||||
|
||||
Bridge {
|
||||
backend: root
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
onClosing: {
|
||||
Qt.quit()
|
||||
}
|
||||
}
|
||||
201
internal/frontend/qml/BugReportView.qml
Normal file
201
internal/frontend/qml/BugReportView.qml
Normal file
@ -0,0 +1,201 @@
|
||||
// Copyright (c) 2022 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
// ProtonMail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// ProtonMail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import QtQuick 2.13
|
||||
import QtQuick.Layouts 1.12
|
||||
import QtQuick.Controls 2.12
|
||||
|
||||
import Proton 4.0
|
||||
|
||||
SettingsView {
|
||||
id: root
|
||||
|
||||
fillHeight: true
|
||||
|
||||
property var selectedAddress
|
||||
|
||||
Label {
|
||||
text: qsTr("Report a problem")
|
||||
colorScheme: root.colorScheme
|
||||
type: Label.Heading
|
||||
}
|
||||
|
||||
|
||||
TextArea {
|
||||
id: description
|
||||
property int _minLength: 150
|
||||
property int _maxLength: 800
|
||||
|
||||
label: qsTr("Description")
|
||||
colorScheme: root.colorScheme
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
Layout.minimumHeight: heightForLinesVisible(4)
|
||||
hint: description.text.length + "/" + _maxLength
|
||||
placeholderText: qsTr("Tell us what went wrong or isn't working (min. %1 characters).").arg(_minLength)
|
||||
|
||||
validator: function(text) {
|
||||
if (description.text.length < description._minLength) {
|
||||
return qsTr("Enter a problem description (min. %1 characters).").arg(_minLength)
|
||||
}
|
||||
|
||||
if (description.text.length > description._maxLength) {
|
||||
return qsTr("Enter a problem description (max. %1 characters).").arg(_maxLength)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
onTextChanged: {
|
||||
// Rise max length error imidiatly while typing
|
||||
if (description.text.length > description._maxLength) {
|
||||
validate()
|
||||
}
|
||||
}
|
||||
|
||||
KeyNavigation.priority: KeyNavigation.BeforeItem
|
||||
KeyNavigation.tab: address
|
||||
|
||||
// set implicitHeight to explicit height because se don't
|
||||
// want TextArea implicitHeight (which is height of all text)
|
||||
// to be considered in SettingsView internal scroll view
|
||||
implicitHeight: height
|
||||
}
|
||||
|
||||
|
||||
TextField {
|
||||
id: address
|
||||
|
||||
label: qsTr("Your contact email")
|
||||
colorScheme: root.colorScheme
|
||||
Layout.fillWidth: true
|
||||
placeholderText: qsTr("e.g. jane.doe@protonmail.com")
|
||||
|
||||
validator: function(str) {
|
||||
if (!isValidEmail(str)) {
|
||||
return qsTr("Enter valid email address")
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
TextField {
|
||||
id: emailClient
|
||||
|
||||
label: qsTr("Your email client (including version)")
|
||||
colorScheme: root.colorScheme
|
||||
Layout.fillWidth: true
|
||||
placeholderText: qsTr("e.g. Apple Mail 14.0")
|
||||
|
||||
validator: function(str) {
|
||||
if (str.length === 0) {
|
||||
return qsTr("Enter an email client name and version")
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
RowLayout {
|
||||
CheckBox {
|
||||
id: includeLogs
|
||||
text: qsTr("Include my recent logs")
|
||||
colorScheme: root.colorScheme
|
||||
checked: true
|
||||
}
|
||||
Button {
|
||||
Layout.leftMargin: 12
|
||||
text: qsTr("View logs")
|
||||
secondary: true
|
||||
colorScheme: root.colorScheme
|
||||
onClicked: Qt.openUrlExternally(root.backend.logsPath)
|
||||
}
|
||||
}
|
||||
|
||||
TextEdit {
|
||||
text: {
|
||||
var address = "bridge@protonmail.com"
|
||||
var mailTo = `<a href="mailto://${address}">${address}</a>`
|
||||
return "<style>a:link { color: " + root.colorScheme.interaction_norm + "; }</style>" +qsTr(
|
||||
"These reports are not end-to-end encrypted. In case of sensitive information, contact us at %1."
|
||||
).arg(mailTo)
|
||||
}
|
||||
onLinkActivated: Qt.openUrlExternally(link)
|
||||
textFormat: Text.RichText
|
||||
|
||||
readOnly: true
|
||||
|
||||
|
||||
Layout.fillWidth: true
|
||||
color: root.colorScheme.text_weak
|
||||
font.family: ProtonStyle.font_family
|
||||
font.weight: ProtonStyle.fontWeight_400
|
||||
font.pixelSize: ProtonStyle.caption_font_size
|
||||
font.letterSpacing: ProtonStyle.caption_letter_spacing
|
||||
// No way to set lineHeight: Style.caption_line_height
|
||||
selectionColor: root.colorScheme.interaction_norm
|
||||
selectedTextColor: root.colorScheme.text_invert
|
||||
wrapMode: Text.WordWrap
|
||||
selectByMouse: true
|
||||
}
|
||||
|
||||
Button {
|
||||
id: sendButton
|
||||
text: qsTr("Send")
|
||||
colorScheme: root.colorScheme
|
||||
|
||||
onClicked: {
|
||||
description.validate()
|
||||
address.validate()
|
||||
emailClient.validate()
|
||||
|
||||
if (description.error || address.error || emailClient.error) {
|
||||
return
|
||||
}
|
||||
|
||||
submit()
|
||||
}
|
||||
|
||||
Connections {target: root.backend; onReportBugFinished: sendButton.loading = false }
|
||||
}
|
||||
|
||||
function setDefaultValue() {
|
||||
description.text = ""
|
||||
address.text = root.selectedAddress
|
||||
emailClient.text = root.backend.currentEmailClient
|
||||
includeLogs.checked = true
|
||||
}
|
||||
|
||||
function isValidEmail(text){
|
||||
var reEmail = /\w+@\w+\.\w+/
|
||||
return reEmail.test(text)
|
||||
}
|
||||
|
||||
function submit() {
|
||||
sendButton.loading = true
|
||||
root.backend.reportBug(
|
||||
description.text,
|
||||
address.text,
|
||||
emailClient.text,
|
||||
includeLogs.checked
|
||||
)
|
||||
}
|
||||
|
||||
onVisibleChanged: {
|
||||
root.setDefaultValue()
|
||||
}
|
||||
}
|
||||
73
internal/frontend/qml/Configuration.qml
Normal file
73
internal/frontend/qml/Configuration.qml
Normal file
@ -0,0 +1,73 @@
|
||||
// Copyright (c) 2022 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
// ProtonMail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// ProtonMail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import QtQuick 2.13
|
||||
import QtQuick.Layouts 1.12
|
||||
import QtQuick.Controls 2.12
|
||||
import QtQuick.Controls.impl 2.12
|
||||
|
||||
import Proton 4.0
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
property ColorScheme colorScheme
|
||||
property string title
|
||||
property string hostname
|
||||
property string port
|
||||
property string username
|
||||
property string password
|
||||
property string security
|
||||
|
||||
implicitWidth: 304
|
||||
implicitHeight: content.height + 2*root._margin
|
||||
|
||||
color: root.colorScheme.background_norm
|
||||
radius: 9
|
||||
|
||||
property int _margin: 24
|
||||
|
||||
ColumnLayout {
|
||||
id: content
|
||||
width: root.width - 2*root._margin
|
||||
anchors{
|
||||
top: root.top
|
||||
left: root.left
|
||||
leftMargin : root._margin
|
||||
rightMargin : root._margin
|
||||
topMargin : root._margin
|
||||
bottomMargin : root._margin
|
||||
}
|
||||
|
||||
spacing: 12
|
||||
|
||||
Label {
|
||||
colorScheme: root.colorScheme
|
||||
text: root.title
|
||||
type: Label.Body_semibold
|
||||
}
|
||||
|
||||
Item{}
|
||||
|
||||
ConfigurationItem{ colorScheme: root.colorScheme; label: qsTr("Hostname") ; value: root.hostname }
|
||||
ConfigurationItem{ colorScheme: root.colorScheme; label: qsTr("Port") ; value: root.port }
|
||||
ConfigurationItem{ colorScheme: root.colorScheme; label: qsTr("Username") ; value: root.username }
|
||||
ConfigurationItem{ colorScheme: root.colorScheme; label: qsTr("Password") ; value: root.password }
|
||||
ConfigurationItem{ colorScheme: root.colorScheme; label: qsTr("Security") ; value: root.security }
|
||||
}
|
||||
}
|
||||
|
||||
89
internal/frontend/qml/ConfigurationItem.qml
Normal file
89
internal/frontend/qml/ConfigurationItem.qml
Normal file
@ -0,0 +1,89 @@
|
||||
// Copyright (c) 2022 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
// ProtonMail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// ProtonMail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import QtQuick 2.13
|
||||
import QtQuick.Layouts 1.12
|
||||
import QtQuick.Controls 2.12
|
||||
import QtQuick.Controls.impl 2.12
|
||||
|
||||
import Proton 4.0
|
||||
|
||||
Item {
|
||||
id: root
|
||||
Layout.fillWidth: true
|
||||
|
||||
property var colorScheme
|
||||
property string label
|
||||
property string value
|
||||
|
||||
implicitHeight: children[0].implicitHeight
|
||||
implicitWidth: children[0].implicitWidth
|
||||
|
||||
ColumnLayout {
|
||||
width: root.width
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
|
||||
ColumnLayout {
|
||||
Label {
|
||||
colorScheme: root.colorScheme
|
||||
text: root.label
|
||||
type: Label.Body
|
||||
}
|
||||
TextEdit {
|
||||
id: valueText
|
||||
text: root.value
|
||||
color: root.colorScheme.text_weak
|
||||
readOnly: true
|
||||
selectByMouse: true
|
||||
selectByKeyboard: true
|
||||
selectionColor: root.colorScheme.text_weak
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
ColorImage {
|
||||
source: "icons/ic-copy.svg"
|
||||
color: root.colorScheme.text_norm
|
||||
height: root.colorScheme.body_font_size
|
||||
sourceSize.height: root.colorScheme.body_font_size
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked : {
|
||||
valueText.select(0, valueText.length)
|
||||
valueText.copy()
|
||||
valueText.deselect()
|
||||
}
|
||||
onPressed: parent.scale = 0.90
|
||||
onReleased: parent.scale = 1
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
height: 1
|
||||
color: root.colorScheme.border_norm
|
||||
}
|
||||
}
|
||||
}
|
||||
408
internal/frontend/qml/ContentWrapper.qml
Normal file
408
internal/frontend/qml/ContentWrapper.qml
Normal file
@ -0,0 +1,408 @@
|
||||
// Copyright (c) 2022 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
// ProtonMail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// ProtonMail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import QtQuick 2.13
|
||||
import QtQuick.Layouts 1.12
|
||||
import QtQuick.Controls 2.12
|
||||
|
||||
import Proton 4.0
|
||||
import Notifications 1.0
|
||||
|
||||
Item {
|
||||
id: root
|
||||
property ColorScheme colorScheme
|
||||
|
||||
property var backend
|
||||
property var notifications
|
||||
|
||||
signal showSetupGuide(var user, string address)
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
spacing: 0
|
||||
|
||||
Rectangle {
|
||||
id: leftBar
|
||||
property ColorScheme colorScheme: root.colorScheme.prominent
|
||||
|
||||
Layout.minimumWidth: 264
|
||||
Layout.maximumWidth: 320
|
||||
Layout.preferredWidth: 320
|
||||
Layout.fillHeight: true
|
||||
|
||||
color: colorScheme.background_norm
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
spacing: 0
|
||||
|
||||
RowLayout {
|
||||
id:topLeftBar
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.minimumHeight: 60
|
||||
Layout.maximumHeight: 60
|
||||
Layout.preferredHeight: 60
|
||||
spacing: 0
|
||||
|
||||
Status {
|
||||
Layout.leftMargin: 16
|
||||
Layout.topMargin: 24
|
||||
Layout.bottomMargin: 17
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
|
||||
colorScheme: leftBar.colorScheme
|
||||
backend: root.backend
|
||||
notifications: root.notifications
|
||||
|
||||
notificationWhitelist: Notifications.Group.Connection | Notifications.Group.ForceUpdate
|
||||
}
|
||||
|
||||
// just a placeholder
|
||||
Item {
|
||||
Layout.fillHeight: true
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Button {
|
||||
colorScheme: leftBar.colorScheme
|
||||
Layout.minimumHeight: 36
|
||||
Layout.maximumHeight: 36
|
||||
Layout.preferredHeight: 36
|
||||
Layout.minimumWidth: 36
|
||||
Layout.maximumWidth: 36
|
||||
Layout.preferredWidth: 36
|
||||
|
||||
Layout.topMargin: 16
|
||||
Layout.bottomMargin: 9
|
||||
Layout.rightMargin: 4
|
||||
|
||||
horizontalPadding: 0
|
||||
|
||||
icon.source: "./icons/ic-question-circle.svg"
|
||||
|
||||
onClicked: rightContent.showHelpView()
|
||||
}
|
||||
|
||||
Button {
|
||||
colorScheme: leftBar.colorScheme
|
||||
Layout.minimumHeight: 36
|
||||
Layout.maximumHeight: 36
|
||||
Layout.preferredHeight: 36
|
||||
Layout.minimumWidth: 36
|
||||
Layout.maximumWidth: 36
|
||||
Layout.preferredWidth: 36
|
||||
|
||||
Layout.topMargin: 16
|
||||
Layout.bottomMargin: 9
|
||||
Layout.rightMargin: 16
|
||||
|
||||
horizontalPadding: 0
|
||||
|
||||
icon.source: "./icons/ic-cog-wheel.svg"
|
||||
|
||||
onClicked: rightContent.showGeneralSettings()
|
||||
}
|
||||
}
|
||||
|
||||
Item {implicitHeight:10}
|
||||
|
||||
// Separator line
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.minimumHeight: 1
|
||||
Layout.maximumHeight: 1
|
||||
color: leftBar.colorScheme.border_weak
|
||||
}
|
||||
|
||||
ListView {
|
||||
id: accounts
|
||||
|
||||
property var _topBottomMargins: 24
|
||||
property var _leftRightMargins: 16
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
Layout.leftMargin: accounts._leftRightMargins
|
||||
Layout.rightMargin: accounts._leftRightMargins
|
||||
Layout.topMargin: accounts._topBottomMargins
|
||||
Layout.bottomMargin: accounts._topBottomMargins
|
||||
|
||||
spacing: 12
|
||||
clip: true
|
||||
boundsBehavior: Flickable.StopAtBounds
|
||||
|
||||
header: Rectangle {
|
||||
height: headerLabel.height+16
|
||||
// color: ProtonStyle.transparent
|
||||
Label{
|
||||
colorScheme: leftBar.colorScheme
|
||||
id: headerLabel
|
||||
text: qsTr("Accounts")
|
||||
type: Label.LabelType.Body
|
||||
}
|
||||
}
|
||||
|
||||
highlight: Rectangle {
|
||||
color: leftBar.colorScheme.interaction_default_active
|
||||
radius: 4
|
||||
}
|
||||
|
||||
model: root.backend.users
|
||||
delegate: Item {
|
||||
width: leftBar.width - 2*accounts._leftRightMargins
|
||||
implicitHeight: children[0].implicitHeight + children[0].anchors.topMargin + children[0].anchors.bottomMargin
|
||||
implicitWidth: children[0].implicitWidth + children[0].anchors.leftMargin + children[0].anchors.rightMargin
|
||||
|
||||
AccountDelegate {
|
||||
id: accountDelegate
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.topMargin: 8
|
||||
anchors.bottomMargin: 8
|
||||
anchors.leftMargin: 12
|
||||
anchors.rightMargin: 12
|
||||
|
||||
colorScheme: leftBar.colorScheme
|
||||
user: root.backend.users.get(index)
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
var user = root.backend.users.get(index)
|
||||
accounts.currentIndex = index
|
||||
if (!user) return
|
||||
if (user.loggedIn) {
|
||||
rightContent.showAccount()
|
||||
} else {
|
||||
signIn.username = user.username
|
||||
rightContent.showSignIn()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Separator
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.minimumHeight: 1
|
||||
Layout.maximumHeight: 1
|
||||
color: leftBar.colorScheme.border_weak
|
||||
}
|
||||
|
||||
Item {
|
||||
id: bottomLeftBar
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.minimumHeight: 52
|
||||
Layout.maximumHeight: 52
|
||||
Layout.preferredHeight: 52
|
||||
|
||||
Button {
|
||||
colorScheme: leftBar.colorScheme
|
||||
width: 36
|
||||
height: 36
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.top: parent.top
|
||||
|
||||
anchors.leftMargin: 16
|
||||
anchors.topMargin: 7
|
||||
|
||||
horizontalPadding: 0
|
||||
|
||||
icon.source: "./icons/ic-plus.svg"
|
||||
|
||||
onClicked: {
|
||||
signIn.username = ""
|
||||
rightContent.showSignIn()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle { // right content background
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
|
||||
color: colorScheme.background_norm
|
||||
|
||||
StackLayout {
|
||||
id: rightContent
|
||||
anchors.fill: parent
|
||||
|
||||
AccountView { // 0
|
||||
colorScheme: root.colorScheme
|
||||
backend: root.backend
|
||||
notifications: root.notifications
|
||||
user: {
|
||||
if (accounts.currentIndex < 0) return undefined
|
||||
if (root.backend.users.count == 0) return undefined
|
||||
return root.backend.users.get(accounts.currentIndex)
|
||||
}
|
||||
onShowSignIn: {
|
||||
signIn.username = this.user.username
|
||||
rightContent.showSignIn()
|
||||
}
|
||||
onShowSetupGuide: {
|
||||
root.showSetupGuide(user,address)
|
||||
}
|
||||
}
|
||||
|
||||
GridLayout { // 1 Sign In
|
||||
columns: 2
|
||||
|
||||
Button {
|
||||
id: backButton
|
||||
Layout.leftMargin: 18
|
||||
Layout.topMargin: 10
|
||||
Layout.alignment: Qt.AlignTop
|
||||
|
||||
colorScheme: root.colorScheme
|
||||
onClicked: {
|
||||
signIn.abort()
|
||||
rightContent.showAccount()
|
||||
}
|
||||
icon.source: "icons/ic-arrow-left.svg"
|
||||
secondary: true
|
||||
horizontalPadding: 8
|
||||
}
|
||||
|
||||
SignIn {
|
||||
id: signIn
|
||||
Layout.topMargin: 68
|
||||
Layout.leftMargin: 80 - backButton.width - 18
|
||||
Layout.rightMargin: 80
|
||||
Layout.bottomMargin: 68
|
||||
Layout.preferredWidth: 320
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
|
||||
colorScheme: root.colorScheme
|
||||
backend: root.backend
|
||||
}
|
||||
}
|
||||
|
||||
GeneralSettings { // 2
|
||||
colorScheme: root.colorScheme
|
||||
backend: root.backend
|
||||
notifications: root.notifications
|
||||
|
||||
onBack: {
|
||||
rightContent.showAccount()
|
||||
}
|
||||
}
|
||||
|
||||
KeychainSettings { // 3
|
||||
colorScheme: root.colorScheme
|
||||
backend: root.backend
|
||||
|
||||
onBack: {
|
||||
rightContent.showGeneralSettings()
|
||||
}
|
||||
}
|
||||
|
||||
PortSettings { // 4
|
||||
colorScheme: root.colorScheme
|
||||
backend: root.backend
|
||||
|
||||
onBack: {
|
||||
rightContent.showGeneralSettings()
|
||||
}
|
||||
}
|
||||
|
||||
SMTPSettings { // 5
|
||||
colorScheme: root.colorScheme
|
||||
backend: root.backend
|
||||
|
||||
onBack: {
|
||||
rightContent.showGeneralSettings()
|
||||
}
|
||||
}
|
||||
|
||||
LocalCacheSettings { // 6
|
||||
colorScheme: root.colorScheme
|
||||
backend: root.backend
|
||||
notifications: root.notifications
|
||||
|
||||
onBack: {
|
||||
rightContent.showGeneralSettings()
|
||||
}
|
||||
}
|
||||
|
||||
HelpView { // 7
|
||||
colorScheme: root.colorScheme
|
||||
backend: root.backend
|
||||
|
||||
onBack: {
|
||||
rightContent.showAccount()
|
||||
}
|
||||
}
|
||||
|
||||
BugReportView { // 8
|
||||
colorScheme: root.colorScheme
|
||||
backend: root.backend
|
||||
selectedAddress: {
|
||||
if (accounts.currentIndex < 0) return ""
|
||||
if (root.backend.users.count == 0) return ""
|
||||
var user = root.backend.users.get(accounts.currentIndex)
|
||||
if (!user) return ""
|
||||
return user.addresses[0]
|
||||
}
|
||||
|
||||
onBack: {
|
||||
rightContent.showHelpView()
|
||||
}
|
||||
}
|
||||
|
||||
function showAccount(index) {
|
||||
if (index !== undefined && index >= 0){
|
||||
accounts.currentIndex = index
|
||||
}
|
||||
rightContent.currentIndex = 0
|
||||
}
|
||||
|
||||
function showSignIn () { rightContent.currentIndex = 1 }
|
||||
function showGeneralSettings () { rightContent.currentIndex = 2 }
|
||||
function showKeychainSettings () { rightContent.currentIndex = 3 }
|
||||
function showPortSettings () { rightContent.currentIndex = 4 }
|
||||
function showSMTPSettings () { rightContent.currentIndex = 5 }
|
||||
function showLocalCacheSettings () { rightContent.currentIndex = 6 }
|
||||
function showHelpView () { rightContent.currentIndex = 7 }
|
||||
function showBugReport () { rightContent.currentIndex = 8 }
|
||||
|
||||
Connections {
|
||||
target: root.backend
|
||||
|
||||
onLoginFinished: rightContent.showAccount(index)
|
||||
onLoginAlreadyLoggedIn: rightContent.showAccount(index)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function showLocalCacheSettings(){rightContent.showLocalCacheSettings() }
|
||||
function showSettings(){rightContent.showGeneralSettings() }
|
||||
function showHelp(){rightContent.showHelpView() }
|
||||
function showSignIn(username){
|
||||
signIn.username = username
|
||||
rightContent.showSignIn()
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2021 Proton Technologies AG
|
||||
// Copyright (c) 2022 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
@ -15,35 +15,41 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import QtQuick 2.8
|
||||
import BridgeUI 1.0
|
||||
import ProtonUI 1.0
|
||||
import QtQuick 2.13
|
||||
import QtQuick.Controls 2.12
|
||||
|
||||
import "."
|
||||
import "./Proton"
|
||||
|
||||
Item {
|
||||
Rectangle {
|
||||
anchors.centerIn: parent
|
||||
width: Style.main.width
|
||||
height: 3*Style.main.height/4
|
||||
property var target: parent
|
||||
|
||||
x: target.x
|
||||
y: target.y
|
||||
width: target.width
|
||||
height: target.height
|
||||
|
||||
color: "transparent"
|
||||
//color: "red"
|
||||
border.color: "red"
|
||||
border.width: 1
|
||||
//z: parent.z - 1
|
||||
z: 10000000
|
||||
|
||||
ListView {
|
||||
anchors.fill: parent
|
||||
clip : true
|
||||
model : go.credits.split(";")
|
||||
|
||||
delegate: AccessibleText {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
text: modelData
|
||||
color: Style.main.text
|
||||
font.pointSize : Style.main.fontSize * Style.pt
|
||||
Label {
|
||||
text: parent.width + "x" + parent.height
|
||||
anchors.centerIn: parent
|
||||
color: "black"
|
||||
colorScheme: ProtonStyle.currentStyle
|
||||
}
|
||||
|
||||
footer: ButtonRounded {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
text: qsTr("Close", "close window")
|
||||
onClicked: dialogCredits.hide()
|
||||
}
|
||||
}
|
||||
Rectangle {
|
||||
width: target.implicitWidth
|
||||
height: target.implicitHeight
|
||||
|
||||
color: "transparent"
|
||||
border.color: "green"
|
||||
border.width: 1
|
||||
//z: parent.z - 1
|
||||
z: 10000000
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user