mirror of
https://github.com/ProtonMail/proton-bridge.git
synced 2025-12-10 20:56:51 +00:00
Compare commits
104 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| df318382b7 | |||
| 341487d839 | |||
| 7468ed7dc0 | |||
| c6107dbd4b | |||
| bcef1c36ba | |||
| 6d9d5f35ca | |||
| 6299a6d390 | |||
| 4ab5635293 | |||
| 5c9e9caa2f | |||
| e055acb8eb | |||
| 72c01046e3 | |||
| 46bc8b08dc | |||
| 00b5046653 | |||
| 21d8ef649f | |||
| 6c96643d12 | |||
| 9193205834 | |||
| 15df130d76 | |||
| 4554369292 | |||
| 50d167a983 | |||
| 7065211064 | |||
| 0069eb9a0c | |||
| 35b5b925cf | |||
| d4df2ea348 | |||
| 0159f24f17 | |||
| 619d5eaec9 | |||
| 52804c7039 | |||
| 837e0d3758 | |||
| fa3829cbfe | |||
| 0679b99a65 | |||
| 4ffa62f6ca | |||
| 0c458f709f | |||
| d1daa02b35 | |||
| 26cdfdeba9 | |||
| f4405b5186 | |||
| 76dda10572 | |||
| 0cde1ab801 | |||
| d9c9edf4d7 | |||
| 29f034abdc | |||
| 62a64cde61 | |||
| 9747145a3c | |||
| e2a30d1ac6 | |||
| 3168cbb77d | |||
| 0a0cc0a62c | |||
| adcf0827ee | |||
| b9ee4a152a | |||
| cb839ff149 | |||
| 45efdad27e | |||
| 6ef2bb254d | |||
| 8ab852277c | |||
| 516ca018d3 | |||
| 5117672388 | |||
| a468ce635c | |||
| 5c58089fb7 | |||
| 3e9c4ba614 | |||
| 8cd17addbe | |||
| 2feaba8888 | |||
| 1909ceed67 | |||
| 07c100bd66 | |||
| ab4776c332 | |||
| f17e0d761e | |||
| a5b9f4c3f1 | |||
| a72f52a5ed | |||
| 6523b906af | |||
| 5d246d449c | |||
| 0b39b2adf6 | |||
| 9bb7c828cd | |||
| 10301b8600 | |||
| 32db6b8d44 | |||
| debf015dd0 | |||
| 4013892a47 | |||
| d5277454c6 | |||
| a9f44731dc | |||
| 48808992ec | |||
| 036bc88789 | |||
| 67a7d556ec | |||
| 5ad338e835 | |||
| e442c47eed | |||
| 5380edeeb9 | |||
| e50d1d01da | |||
| 082a803e47 | |||
| 07d9bc0831 | |||
| 4e5a1d4b30 | |||
| 4a54f878c4 | |||
| dc3b4d53e1 | |||
| be583c431e | |||
| 805544ffb0 | |||
| 7b84038bf4 | |||
| e8cbbaa832 | |||
| 56e32e67de | |||
| b36ac532c9 | |||
| d3b0871cf1 | |||
| 7b4204591c | |||
| 4514d72d70 | |||
| dfbf25a9f4 | |||
| 122eac50a6 | |||
| 839708dcfe | |||
| d2066173f0 | |||
| eccad4bbfd | |||
| 98ab794f13 | |||
| b7b2297635 | |||
| dc3f61acee | |||
| 6fffb460b8 | |||
| c677b78f16 | |||
| 014c8af560 |
6
.gitignore
vendored
6
.gitignore
vendored
@ -27,7 +27,11 @@ internal/frontend/qml/ImportExportUI/images
|
|||||||
frontend/qml/*.qmlc
|
frontend/qml/*.qmlc
|
||||||
|
|
||||||
# Build files
|
# Build files
|
||||||
bridge_darwin_*.tgz
|
/launcher-*
|
||||||
|
/bridge_*_*.tgz
|
||||||
|
/ie_*_*.tgz
|
||||||
|
/versioner
|
||||||
|
/hasher
|
||||||
cmd/Desktop-Bridge/deploy
|
cmd/Desktop-Bridge/deploy
|
||||||
cmd/Import-Export/deploy
|
cmd/Import-Export/deploy
|
||||||
internal/frontend/qt*/moc.cpp
|
internal/frontend/qt*/moc.cpp
|
||||||
|
|||||||
@ -106,7 +106,7 @@ build-linux-qa:
|
|||||||
only:
|
only:
|
||||||
- web
|
- web
|
||||||
script:
|
script:
|
||||||
- BUILD_TAGS="build_qa pmapi_qa" make build
|
- BUILD_TAGS="build_qa" make build
|
||||||
artifacts:
|
artifacts:
|
||||||
name: "bridge-linux-qa-$CI_COMMIT_SHORT_SHA"
|
name: "bridge-linux-qa-$CI_COMMIT_SHORT_SHA"
|
||||||
paths:
|
paths:
|
||||||
@ -121,6 +121,17 @@ build-ie-linux:
|
|||||||
paths:
|
paths:
|
||||||
- ie_*.tgz
|
- 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:
|
.build-darwin-base:
|
||||||
extends: .build-base
|
extends: .build-base
|
||||||
before_script:
|
before_script:
|
||||||
@ -150,7 +161,7 @@ build-darwin-qa:
|
|||||||
only:
|
only:
|
||||||
- web
|
- web
|
||||||
script:
|
script:
|
||||||
- BUILD_TAGS="build_qa pmapi_qa" make build
|
- BUILD_TAGS="build_qa" make build
|
||||||
artifacts:
|
artifacts:
|
||||||
name: "bridge-darwin-qa-$CI_COMMIT_SHORT_SHA"
|
name: "bridge-darwin-qa-$CI_COMMIT_SHORT_SHA"
|
||||||
paths:
|
paths:
|
||||||
@ -165,6 +176,17 @@ build-ie-darwin:
|
|||||||
paths:
|
paths:
|
||||||
- ie_*.tgz
|
- 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:
|
.build-windows-base:
|
||||||
extends: .build-base
|
extends: .build-base
|
||||||
services:
|
services:
|
||||||
@ -198,7 +220,7 @@ build-windows-qa:
|
|||||||
- apt-get update && apt-get -y install binutils-mingw-w64 tar gzip
|
- apt-get update && apt-get -y install binutils-mingw-w64 tar gzip
|
||||||
- ln -s /usr/bin/x86_64-w64-mingw32-windres /usr/bin/windres
|
- ln -s /usr/bin/x86_64-w64-mingw32-windres /usr/bin/windres
|
||||||
- go mod download
|
- go mod download
|
||||||
- TARGET_OS=windows BUILD_TAGS="build_qa pmapi_qa" make build
|
- TARGET_OS=windows BUILD_TAGS="build_qa" make build
|
||||||
artifacts:
|
artifacts:
|
||||||
name: "bridge-windows-qa-$CI_COMMIT_SHORT_SHA"
|
name: "bridge-windows-qa-$CI_COMMIT_SHORT_SHA"
|
||||||
paths:
|
paths:
|
||||||
@ -219,6 +241,23 @@ build-ie-windows:
|
|||||||
paths:
|
paths:
|
||||||
- ie_*.tgz
|
- 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
|
# Stage: MIRROR
|
||||||
|
|
||||||
mirror-repo:
|
mirror-repo:
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
# Building ProtonMail Bridge and Import-Export app
|
# Building ProtonMail Bridge and Import-Export app
|
||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
* 64-bit OS (the go-rfc5322 module cannot currently be compiled for 32-bit OSes)
|
||||||
* Go 1.13
|
* Go 1.13
|
||||||
* Bash with basic build utils: make, gcc, sed, find, grep, ...
|
* Bash with basic build utils: make, gcc, sed, find, grep, ...
|
||||||
* For Windows it is recommended to use MinGW 64bit shell from [MSYS2](https://www.msys2.org/)
|
* For Windows it is recommended to use MinGW 64bit shell from [MSYS2](https://www.msys2.org/)
|
||||||
@ -63,6 +64,11 @@ make build-ie
|
|||||||
* for `windows`, the binary will have the file extension `.exe` (e.g `proton-bridge.exe`)
|
* for `windows`, the binary will have the file extension `.exe` (e.g `proton-bridge.exe`)
|
||||||
* for `darwin`, the application will be created with name of the project directory (e.g `proton-bridge.app`)
|
* for `darwin`, the application will be created with name of the project directory (e.g `proton-bridge.app`)
|
||||||
|
|
||||||
|
### Launchers
|
||||||
|
Launchers are only included in official distributions and provide the public
|
||||||
|
key used to verify signed app binaries, allowing the automatic update feature.
|
||||||
|
See README for more information.
|
||||||
|
|
||||||
### Tags
|
### Tags
|
||||||
Note that repository contains both Bridge and Import-Export apps and they are
|
Note that repository contains both Bridge and Import-Export apps and they are
|
||||||
not released together. Therefore, each app has own tag prefix. Bridge tags
|
not released together. Therefore, each app has own tag prefix. Bridge tags
|
||||||
|
|||||||
89
Changelog.md
89
Changelog.md
@ -2,6 +2,91 @@
|
|||||||
|
|
||||||
Changelog [format](http://keepachangelog.com/en/1.0.0/)
|
Changelog [format](http://keepachangelog.com/en/1.0.0/)
|
||||||
|
|
||||||
|
## [Bridge 1.6.3] HZM
|
||||||
|
|
||||||
|
### Added
|
||||||
|
* GODT-337 Desktop files.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
* GODT-885 Do not explicitly unlabel folders during move to match behaviour of other clients.
|
||||||
|
* GODT-616 Better user message about wrong mailbox password.
|
||||||
|
* GODT-1021 Do not allow copy Inbox->Sent or Sent->Inbox
|
||||||
|
* GODT-976 Exclude updates from clearing cache and clear cache, including updates, while switching early access off
|
||||||
|
* GODT-1033 Retry starting IMAP server after connection was down
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
* GODT-1011 Stable integration test deleting many messages using UID EXPUNGE.
|
||||||
|
* GODT-1015 Use lenient version parser to properly parse version provided by Mac.
|
||||||
|
* GODT-919 Notify about update right after the start.
|
||||||
|
* GODT-919 GODT-1022 Logs and signals
|
||||||
|
|
||||||
|
|
||||||
|
## [IE 1.3.0] Farg
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
* GODT-1019 Remove dependency on go-apple-mobileconfig.
|
||||||
|
* GODT-928 Reject messages which are too large.
|
||||||
|
* GODT-999 Sending: do not send empty objects to API.
|
||||||
|
|
||||||
|
## [Bridge 1.6.2] HZM
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
* GODT-1010 Strip angle brackets from external ID.
|
||||||
|
|
||||||
|
## [Bridge 1.6.1] HZM
|
||||||
|
|
||||||
|
### Added
|
||||||
|
* GODT-1007 Notify user when version is the latest.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
* GODT-787 GODT-978 Fix IE and Bridge importing to Sent not showing up in Inbox (setting up flags properly).
|
||||||
|
* GODT-1006 Use correct macOS keychain name.
|
||||||
|
* GODT-1009 Set ContentID if present and not explicitly attachment.
|
||||||
|
* GODT-1008 Transparent welcome message.
|
||||||
|
|
||||||
|
|
||||||
|
## [Bridge 1.6.0] HZM
|
||||||
|
|
||||||
|
### Added
|
||||||
|
* GODT-705 Allow silent update in Bridge and Import-Export app.
|
||||||
|
* GODT-958 Release notes per eaach update channel.
|
||||||
|
* GODT-875 Added GUI dialog on force update.
|
||||||
|
* GODT-820 Added GUI notification on impossibility of update installation (both silent and manual).
|
||||||
|
* GODT-870 Added GUI notification on error during silent update.
|
||||||
|
* GODT-805 Added GUI notification on update available.
|
||||||
|
* GODT-804 Added GUI notification on silent update installed (promt to restart).
|
||||||
|
* GODT-275 Added option to disable autoupdates in settings (default autoupdate is enabled).
|
||||||
|
* GODT-874 Added manual triggers to Updater module.
|
||||||
|
* GODT-851 Added support of UID EXPUNGE.
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
* GODT-248 Remove dependency on go-appdir.
|
||||||
|
* GODT-208 Remove deprecated use of BuildNameToCertificate.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
* Check deprecated status code first to better determine API error.
|
||||||
|
* GODT-831 Fix reporting bug from accounts with empty account name.
|
||||||
|
* GODT-831 Cancel request of uploading attachment if reading/writing it fails.
|
||||||
|
* GODT-991 Fix panic when stopping import progress during loading mailboxes info.
|
||||||
|
* GODT-895 Fix panic when modifying addresses during changing address mode.
|
||||||
|
* GODT-946 Fix flaky tests notifying changes.
|
||||||
|
* GODT-979 Fix panic when trying to parse a multipart/alternative section that has no child sections.
|
||||||
|
* GODT-900 Remove \Deleted flag after re-importing the message (do not delete messages by moving to local folder and back).
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
* Rename channels `beta->early`, `live->stable`.
|
||||||
|
* Bump gopenpgp dependency to v2.1.3 for improved memory usage.
|
||||||
|
* GODT-97 Don't log errors caused by SELECT "".
|
||||||
|
* GODT-806 GUI dialog on manual update. Added autoupdates checkbox. Simplifyed installation process GUI.
|
||||||
|
* GODT-912 Scroll bar behaviour in settings tab.
|
||||||
|
* GODT-149 Send heartbeat ASAP on each new calendar day.
|
||||||
|
* GODT-792 Stop IMAP server while no internet connection.
|
||||||
|
* GODT-792 Cache message size every time to reduce network traffic.
|
||||||
|
* GODT-792 Cache body structure in order to reduce network traffic.
|
||||||
|
* GODT-792 GODT-908 Cache body structure in order to reduce network traffic.
|
||||||
|
* GODT-908 Do not unpause event loop if other mailbox is still fetching.
|
||||||
|
|
||||||
|
|
||||||
## [Bridge 1.5.7] Golden Gate
|
## [Bridge 1.5.7] Golden Gate
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
@ -73,7 +158,6 @@ CSB-331 Fix sending error due to mixed case in sender address.
|
|||||||
* GODT-878 Tests for send packet creation logic.
|
* GODT-878 Tests for send packet creation logic.
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
* GODT-180 Updated Sentry client.
|
|
||||||
* GODT-651 Build creates proper binary names.
|
* GODT-651 Build creates proper binary names.
|
||||||
* GODT-878 Fix an issue where the random session key is inadvertently sent to
|
* GODT-878 Fix an issue where the random session key is inadvertently sent to
|
||||||
the Proton server. The data payload is always encrypted within TLS, but this
|
the Proton server. The data payload is always encrypted within TLS, but this
|
||||||
@ -114,6 +198,7 @@ CSB-331 Fix sending error due to mixed case in sender address.
|
|||||||
* GODT-763 Detect Gmail labels from All Mail mbox export (using X-Gmail-Label header).
|
* GODT-763 Detect Gmail labels from All Mail mbox export (using X-Gmail-Label header).
|
||||||
* GODT-834 Info about tags in BUILDS.md and link to Import-Export page in README.md.
|
* GODT-834 Info about tags in BUILDS.md and link to Import-Export page in README.md.
|
||||||
* GODT-777 Support Apple Mail MBOX export format.
|
* GODT-777 Support Apple Mail MBOX export format.
|
||||||
|
* GODT-731 Re-open Import-Export app from the second instance.
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
* GODT-677 Windows IE: global import settings not fit in window.
|
* GODT-677 Windows IE: global import settings not fit in window.
|
||||||
@ -170,6 +255,7 @@ CSB-331 Fix sending error due to mixed case in sender address.
|
|||||||
### Fixed
|
### Fixed
|
||||||
* GODT-770 Better handling of extraneous end-of-mail indicator.
|
* GODT-770 Better handling of extraneous end-of-mail indicator.
|
||||||
* GODT-776 Fix crash when IMAP client connects while account is logging in.
|
* GODT-776 Fix crash when IMAP client connects while account is logging in.
|
||||||
|
* GODT-744 User agent not being sent to sentry.
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
* Bump crypto version to v0.0.0-20200818122824-ed5d25e28db8.
|
* Bump crypto version to v0.0.0-20200818122824-ed5d25e28db8.
|
||||||
@ -203,6 +289,7 @@ CSB-331 Fix sending error due to mixed case in sender address.
|
|||||||
* GODT-682 Persistent anonymous API cookies for Import-Export.
|
* GODT-682 Persistent anonymous API cookies for Import-Export.
|
||||||
* GODT-357 Use go-message to make a better message parser.
|
* GODT-357 Use go-message to make a better message parser.
|
||||||
* GODT-720 Time measurement of progress for Import-Export.
|
* GODT-720 Time measurement of progress for Import-Export.
|
||||||
|
* GODT-693 Launcher.
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
* GODT-511 User agent format changed.
|
* GODT-511 User agent format changed.
|
||||||
|
|||||||
85
Makefile
85
Makefile
@ -7,17 +7,18 @@ TARGET_CMD?=Desktop-Bridge
|
|||||||
TARGET_OS?=${GOOS}
|
TARGET_OS?=${GOOS}
|
||||||
|
|
||||||
## Build
|
## Build
|
||||||
.PHONY: build build-ie build-nogui build-ie-nogui check-has-go
|
.PHONY: build build-ie build-nogui build-ie-nogui build-launcher build-launcher-ie versioner hasher
|
||||||
|
|
||||||
# Keep version hardcoded so app build works also without Git repository.
|
# Keep version hardcoded so app build works also without Git repository.
|
||||||
BRIDGE_APP_VERSION?=1.5.7-git
|
BRIDGE_APP_VERSION?=1.6.3+git
|
||||||
IE_APP_VERSION?=1.2.3-git
|
IE_APP_VERSION?=1.3.0+git
|
||||||
APP_VERSION:=${BRIDGE_APP_VERSION}
|
APP_VERSION:=${BRIDGE_APP_VERSION}
|
||||||
SRC_ICO:=logo.ico
|
SRC_ICO:=logo.ico
|
||||||
SRC_ICNS:=Bridge.icns
|
SRC_ICNS:=Bridge.icns
|
||||||
SRC_SVG:=logo.svg
|
SRC_SVG:=logo.svg
|
||||||
TGT_ICNS:=Bridge.icns
|
TGT_ICNS:=Bridge.icns
|
||||||
EXE_NAME:=proton-bridge
|
EXE_NAME:=proton-bridge
|
||||||
|
CONFIGNAME:=bridge
|
||||||
ifeq "${TARGET_CMD}" "Import-Export"
|
ifeq "${TARGET_CMD}" "Import-Export"
|
||||||
APP_VERSION:=${IE_APP_VERSION}
|
APP_VERSION:=${IE_APP_VERSION}
|
||||||
SRC_ICO:=ie.ico
|
SRC_ICO:=ie.ico
|
||||||
@ -25,20 +26,27 @@ ifeq "${TARGET_CMD}" "Import-Export"
|
|||||||
SRC_SVG:=ie.svg
|
SRC_SVG:=ie.svg
|
||||||
TGT_ICNS:=ImportExport.icns
|
TGT_ICNS:=ImportExport.icns
|
||||||
EXE_NAME:=proton-ie
|
EXE_NAME:=proton-ie
|
||||||
|
CONFIGNAME:=importExport
|
||||||
endif
|
endif
|
||||||
REVISION:=$(shell git rev-parse --short=10 HEAD)
|
REVISION:=$(shell git rev-parse --short=10 HEAD)
|
||||||
BUILD_TIME:=$(shell date +%FT%T%z)
|
BUILD_TIME:=$(shell date +%FT%T%z)
|
||||||
|
|
||||||
BUILD_TAGS?=pmapi_prod
|
|
||||||
BUILD_FLAGS:=-tags='${BUILD_TAGS}'
|
BUILD_FLAGS:=-tags='${BUILD_TAGS}'
|
||||||
|
BUILD_FLAGS_LAUNCHER:=${BUILD_FLAGS}
|
||||||
BUILD_FLAGS_NOGUI:=-tags='${BUILD_TAGS} nogui'
|
BUILD_FLAGS_NOGUI:=-tags='${BUILD_TAGS} nogui'
|
||||||
GO_LDFLAGS:=$(addprefix -X github.com/ProtonMail/proton-bridge/pkg/constants.,Version=${APP_VERSION} Revision=${REVISION} BuildTime=${BUILD_TIME})
|
GO_LDFLAGS:=$(addprefix -X github.com/ProtonMail/proton-bridge/internal/constants.,Version=${APP_VERSION} Revision=${REVISION} BuildTime=${BUILD_TIME})
|
||||||
ifneq "${BUILD_LDFLAGS}" ""
|
ifneq "${BUILD_LDFLAGS}" ""
|
||||||
GO_LDFLAGS+= ${BUILD_LDFLAGS}
|
GO_LDFLAGS+=${BUILD_LDFLAGS}
|
||||||
endif
|
endif
|
||||||
GO_LDFLAGS:=-ldflags '${GO_LDFLAGS}'
|
GO_LDFLAGS_LAUNCHER:=${GO_LDFLAGS}
|
||||||
BUILD_FLAGS+= ${GO_LDFLAGS}
|
GO_LDFLAGS_LAUNCHER+=$(addprefix -X main.,ConfigName=${CONFIGNAME} ExeName=proton-${APP})
|
||||||
BUILD_FLAGS_NOGUI+= ${GO_LDFLAGS}
|
ifeq "${TARGET_OS}" "windows"
|
||||||
|
GO_LDFLAGS_LAUNCHER+=-H=windowsgui
|
||||||
|
endif
|
||||||
|
|
||||||
|
BUILD_FLAGS+=-ldflags '${GO_LDFLAGS}'
|
||||||
|
BUILD_FLAGS_NOGUI+=-ldflags '${GO_LDFLAGS}'
|
||||||
|
BUILD_FLAGS_LAUNCHER+=-ldflags '${GO_LDFLAGS_LAUNCHER}'
|
||||||
|
|
||||||
DEPLOY_DIR:=cmd/${TARGET_CMD}/deploy
|
DEPLOY_DIR:=cmd/${TARGET_CMD}/deploy
|
||||||
ICO_FILES:=
|
ICO_FILES:=
|
||||||
@ -71,6 +79,7 @@ else
|
|||||||
endif
|
endif
|
||||||
|
|
||||||
build: ${TGZ_TARGET}
|
build: ${TGZ_TARGET}
|
||||||
|
|
||||||
build-ie:
|
build-ie:
|
||||||
TARGET_CMD=Import-Export $(MAKE) build
|
TARGET_CMD=Import-Export $(MAKE) build
|
||||||
|
|
||||||
@ -80,14 +89,27 @@ build-nogui:
|
|||||||
build-ie-nogui:
|
build-ie-nogui:
|
||||||
TARGET_CMD=Import-Export $(MAKE) build-nogui
|
TARGET_CMD=Import-Export $(MAKE) build-nogui
|
||||||
|
|
||||||
|
build-launcher:
|
||||||
|
go build ${BUILD_FLAGS_LAUNCHER} -o launcher-${APP} cmd/launcher/main.go
|
||||||
|
|
||||||
|
build-launcher-ie:
|
||||||
|
TARGET_CMD=Import-Export $(MAKE) build-launcher
|
||||||
|
|
||||||
|
versioner:
|
||||||
|
go build ${BUILD_FLAGS} -o versioner utils/versioner/main.go
|
||||||
|
|
||||||
|
hasher:
|
||||||
|
go build -o hasher utils/hasher/main.go
|
||||||
|
|
||||||
${TGZ_TARGET}: ${DEPLOY_DIR}/${TARGET_OS}
|
${TGZ_TARGET}: ${DEPLOY_DIR}/${TARGET_OS}
|
||||||
rm -f $@
|
rm -f $@
|
||||||
cd ${DEPLOY_DIR} && tar czf ../../../$@ ${TARGET_OS}
|
cd ${DEPLOY_DIR}/${TARGET_OS} && tar czf ../../../../$@ .
|
||||||
|
|
||||||
${DEPLOY_DIR}/linux: ${EXE_TARGET}
|
${DEPLOY_DIR}/linux: ${EXE_TARGET}
|
||||||
cp -pf ./internal/frontend/share/icons/${SRC_SVG} ${DEPLOY_DIR}/linux/logo.svg
|
cp -pf ./internal/frontend/share/icons/${SRC_SVG} ${DEPLOY_DIR}/linux/logo.svg
|
||||||
cp -pf ./LICENSE ${DEPLOY_DIR}/linux/
|
cp -pf ./LICENSE ${DEPLOY_DIR}/linux/
|
||||||
cp -pf ./Changelog.md ${DEPLOY_DIR}/linux/
|
cp -pf ./Changelog.md ${DEPLOY_DIR}/linux/
|
||||||
|
cp -pf ./dist/${EXE_NAME}.desktop ${DEPLOY_DIR}/linux/
|
||||||
|
|
||||||
${DEPLOY_DIR}/darwin: ${EXE_TARGET}
|
${DEPLOY_DIR}/darwin: ${EXE_TARGET}
|
||||||
if [ "${DIRNAME}" != "${EXE_NAME}" ]; then \
|
if [ "${DIRNAME}" != "${EXE_NAME}" ]; then \
|
||||||
@ -177,7 +199,7 @@ install-go-mod-outdated:
|
|||||||
|
|
||||||
|
|
||||||
## Checks, mocks and docs
|
## Checks, mocks and docs
|
||||||
.PHONY: check-has-go add-license change-copyright-year test bench coverage mocks lint-license lint-golang lint updates doc
|
.PHONY: check-has-go add-license change-copyright-year test bench coverage mocks lint-license lint-golang lint updates doc release-notes
|
||||||
check-has-go:
|
check-has-go:
|
||||||
@which go || (echo "Install Go-lang!" && exit 1)
|
@which go || (echo "Install Go-lang!" && exit 1)
|
||||||
|
|
||||||
@ -192,18 +214,24 @@ test: gofiles
|
|||||||
go test -coverprofile=/tmp/coverage.out -run=${TESTRUN} \
|
go test -coverprofile=/tmp/coverage.out -run=${TESTRUN} \
|
||||||
./internal/api/... \
|
./internal/api/... \
|
||||||
./internal/bridge/... \
|
./internal/bridge/... \
|
||||||
|
./internal/config/... \
|
||||||
|
./internal/constants/... \
|
||||||
|
./internal/cookies/... \
|
||||||
|
./internal/crash/... \
|
||||||
./internal/events/... \
|
./internal/events/... \
|
||||||
./internal/frontend/autoconfig/... \
|
./internal/frontend/autoconfig/... \
|
||||||
./internal/frontend/cli/... \
|
./internal/frontend/cli/... \
|
||||||
./internal/imap/... \
|
./internal/imap/... \
|
||||||
./internal/metrics/... \
|
|
||||||
./internal/importexport/... \
|
./internal/importexport/... \
|
||||||
./internal/preferences/... \
|
./internal/locations/... \
|
||||||
|
./internal/logging/... \
|
||||||
|
./internal/metrics/... \
|
||||||
./internal/smtp/... \
|
./internal/smtp/... \
|
||||||
./internal/store/... \
|
./internal/store/... \
|
||||||
./internal/transfer/... \
|
./internal/transfer/... \
|
||||||
./internal/updates/... \
|
./internal/updater/... \
|
||||||
./internal/users/... \
|
./internal/users/... \
|
||||||
|
./internal/versioner/... \
|
||||||
./pkg/...
|
./pkg/...
|
||||||
|
|
||||||
bench:
|
bench:
|
||||||
@ -215,7 +243,7 @@ coverage: test
|
|||||||
go tool cover -html=/tmp/coverage.out -o=coverage.html
|
go tool cover -html=/tmp/coverage.out -o=coverage.html
|
||||||
|
|
||||||
mocks:
|
mocks:
|
||||||
mockgen --package mocks github.com/ProtonMail/proton-bridge/internal/users Configer,PanicHandler,ClientManager,CredentialsStorer,StoreMaker > internal/users/mocks/mocks.go
|
mockgen --package mocks github.com/ProtonMail/proton-bridge/internal/users Locator,PanicHandler,ClientManager,CredentialsStorer,StoreMaker > internal/users/mocks/mocks.go
|
||||||
mockgen --package mocks github.com/ProtonMail/proton-bridge/internal/transfer PanicHandler,ClientManager,IMAPClientProvider > internal/transfer/mocks/mocks.go
|
mockgen --package mocks github.com/ProtonMail/proton-bridge/internal/transfer PanicHandler,ClientManager,IMAPClientProvider > internal/transfer/mocks/mocks.go
|
||||||
mockgen --package mocks github.com/ProtonMail/proton-bridge/internal/store PanicHandler,ClientManager,BridgeUser,ChangeNotifier > internal/store/mocks/mocks.go
|
mockgen --package mocks github.com/ProtonMail/proton-bridge/internal/store PanicHandler,ClientManager,BridgeUser,ChangeNotifier > 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/listener Listener > internal/store/mocks/utils_mocks.go
|
||||||
@ -227,7 +255,8 @@ lint-license:
|
|||||||
./utils/missing_license.sh check
|
./utils/missing_license.sh check
|
||||||
|
|
||||||
lint-changelog:
|
lint-changelog:
|
||||||
./utils/changelog_linter.sh
|
./utils/changelog_linter.sh Changelog.md
|
||||||
|
./utils/changelog_linter.sh unreleased.md
|
||||||
|
|
||||||
lint-golang:
|
lint-golang:
|
||||||
which golangci-lint || $(MAKE) install-linter
|
which golangci-lint || $(MAKE) install-linter
|
||||||
@ -241,25 +270,28 @@ updates: install-go-mod-outdated
|
|||||||
doc:
|
doc:
|
||||||
godoc -http=:6060
|
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/%.html: release-notes/%.md
|
||||||
|
./utils/release_notes.sh $^
|
||||||
|
|
||||||
.PHONY: gofiles
|
.PHONY: gofiles
|
||||||
# Following files are for the whole app so it makes sense to have them in bridge package.
|
# 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.)
|
# (Options like cmd or internal were considered and bridge package is the best place for them.)
|
||||||
gofiles: ./internal/bridge/credits.go ./internal/bridge/release_notes.go ./internal/importexport/credits.go ./internal/importexport/release_notes.go
|
gofiles: ./internal/bridge/credits.go ./internal/importexport/credits.go
|
||||||
./internal/bridge/credits.go: ./utils/credits.sh go.mod
|
./internal/bridge/credits.go: ./utils/credits.sh go.mod
|
||||||
cd ./utils/ && ./credits.sh bridge
|
cd ./utils/ && ./credits.sh bridge
|
||||||
./internal/bridge/release_notes.go: ./utils/release-notes.sh ./release-notes/notes-bridge.txt ./release-notes/bugs-bridge.txt
|
|
||||||
cd ./utils/ && ./release-notes.sh bridge
|
|
||||||
./internal/importexport/credits.go: ./utils/credits.sh go.mod
|
./internal/importexport/credits.go: ./utils/credits.sh go.mod
|
||||||
cd ./utils/ && ./credits.sh importexport
|
cd ./utils/ && ./credits.sh importexport
|
||||||
./internal/importexport/release_notes.go: ./utils/release-notes.sh ./release-notes/notes-importexport.txt ./release-notes/bugs-importexport.txt
|
|
||||||
cd ./utils/ && ./release-notes.sh importexport
|
|
||||||
|
|
||||||
|
|
||||||
## Run and debug
|
## 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 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
|
||||||
|
|
||||||
VERBOSITY?=debug-client
|
LOG?=debug
|
||||||
RUN_FLAGS:=-m -l=${VERBOSITY}
|
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: run-nogui-cli
|
run: run-nogui-cli
|
||||||
|
|
||||||
@ -304,3 +336,10 @@ clean: clean-vendor
|
|||||||
rm -rf cmd/Import-Export/deploy
|
rm -rf cmd/Import-Export/deploy
|
||||||
rm -f build last.log mem.pprof main.go
|
rm -f build last.log mem.pprof main.go
|
||||||
rm -rf logo.ico icon.rc icon_windows.syso internal/frontend/qt/icon_windows.syso
|
rm -rf logo.ico icon.rc icon_windows.syso internal/frontend/qt/icon_windows.syso
|
||||||
|
rm -f release-notes/bridge.html
|
||||||
|
rm -f release-notes/import-export.html
|
||||||
|
|
||||||
|
.PHONY: generate
|
||||||
|
generate:
|
||||||
|
go generate ./...
|
||||||
|
$(MAKE) add-license
|
||||||
|
|||||||
18
README.md
18
README.md
@ -37,6 +37,18 @@ check the results.
|
|||||||
|
|
||||||
More details [on the public website](https://protonmail.com/import-export).
|
More details [on the public website](https://protonmail.com/import-export).
|
||||||
|
|
||||||
|
## Launchers
|
||||||
|
Launchers are binaries used to run the ProtonMail Bridge or Import-Export apps.
|
||||||
|
|
||||||
|
Official distributions of the ProtonMail Bridge and Import-Export apps contain
|
||||||
|
both a launcher and the app itself. The launcher is installed in a protected
|
||||||
|
area of the system (i.e. an area accessible only with admin privileges) and is
|
||||||
|
used to run the app. The launcher ensures that nobody tampered with the app's
|
||||||
|
files by verifying their signature using a hardcoded public key. App files are
|
||||||
|
placed in regular userspace and are signed by Proton's private key. This
|
||||||
|
feature enables the app to securely update itself automatically without asking
|
||||||
|
the user for a password.
|
||||||
|
|
||||||
## Keychain
|
## Keychain
|
||||||
You need to have a keychain in order to run the ProtonMail Bridge. On Mac or
|
You need to have a keychain in order to run the ProtonMail Bridge. On Mac or
|
||||||
Windows, Bridge uses native credential managers. On Linux, use
|
Windows, Bridge uses native credential managers. On Linux, use
|
||||||
@ -72,9 +84,9 @@ The database stores metadata necessary for presenting messages and mailboxes to
|
|||||||
|
|
||||||
### Preferences
|
### Preferences
|
||||||
User preferences are stored in json at the following location:
|
User preferences are stored in json at the following location:
|
||||||
- Linux: `~/.cache/protonmail/bridge/<cacheVersion>/prefs.json` (unless `XDG_CACHE_HOME` is set, in which case that is used as your `~`)
|
- Linux: `~/.config/protonmail/bridge/prefs.json`
|
||||||
- macOS: `~/Library/Caches/protonmail/bridge/<cacheVersion>/prefs.json`
|
- macOS: `~/Library/ApplicationSupport/protonmail/bridge/prefs.json`
|
||||||
- Windows: `%LOCALAPPDATA%\protonmail\bridge\<cacheVersion>\prefs.json`
|
- Windows: `%APPDATA%\protonmail\bridge\prefs.json`
|
||||||
|
|
||||||
### IMAP Cache
|
### IMAP Cache
|
||||||
The currently subscribed mailboxes are held in a json file:
|
The currently subscribed mailboxes are held in a json file:
|
||||||
|
|||||||
@ -35,261 +35,40 @@ package main
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
"runtime/pprof"
|
|
||||||
|
|
||||||
"github.com/ProtonMail/proton-bridge/internal/api"
|
"github.com/ProtonMail/proton-bridge/internal/app/base"
|
||||||
"github.com/ProtonMail/proton-bridge/internal/bridge"
|
"github.com/ProtonMail/proton-bridge/internal/app/bridge"
|
||||||
"github.com/ProtonMail/proton-bridge/internal/cmd"
|
|
||||||
"github.com/ProtonMail/proton-bridge/internal/cookies"
|
|
||||||
"github.com/ProtonMail/proton-bridge/internal/events"
|
|
||||||
"github.com/ProtonMail/proton-bridge/internal/frontend"
|
|
||||||
"github.com/ProtonMail/proton-bridge/internal/imap"
|
|
||||||
"github.com/ProtonMail/proton-bridge/internal/preferences"
|
|
||||||
"github.com/ProtonMail/proton-bridge/internal/smtp"
|
|
||||||
"github.com/ProtonMail/proton-bridge/internal/updates"
|
|
||||||
"github.com/ProtonMail/proton-bridge/internal/users/credentials"
|
|
||||||
"github.com/ProtonMail/proton-bridge/pkg/config"
|
|
||||||
"github.com/ProtonMail/proton-bridge/pkg/constants"
|
|
||||||
"github.com/ProtonMail/proton-bridge/pkg/listener"
|
|
||||||
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
|
||||||
"github.com/allan-simon/go-singleinstance"
|
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/urfave/cli"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// cacheVersion is used for cache files such as lock, events, preferences, user_info, db files.
|
appName = "ProtonMail Bridge"
|
||||||
// Different number will drop old files and create new ones.
|
appUsage = "ProtonMail IMAP and SMTP Bridge"
|
||||||
cacheVersion = "c11"
|
configName = "bridge"
|
||||||
|
updateURLName = "bridge"
|
||||||
appName = "bridge"
|
keychainName = "bridge"
|
||||||
)
|
cacheVersion = "c11"
|
||||||
|
|
||||||
var (
|
|
||||||
log = logrus.WithField("pkg", "main") //nolint[gochecknoglobals]
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
cmd.Main(
|
base, err := base.New(
|
||||||
"ProtonMail Bridge",
|
appName,
|
||||||
"ProtonMail IMAP and SMTP Bridge",
|
appUsage,
|
||||||
[]cli.Flag{
|
configName,
|
||||||
cli.BoolFlag{
|
updateURLName,
|
||||||
Name: "no-window",
|
keychainName,
|
||||||
Usage: "Don't show window after start"},
|
cacheVersion,
|
||||||
cli.BoolFlag{
|
|
||||||
Name: "noninteractive",
|
|
||||||
Usage: "Start Bridge entirely noninteractively"},
|
|
||||||
},
|
|
||||||
run,
|
|
||||||
)
|
)
|
||||||
}
|
if err != nil {
|
||||||
|
logrus.WithError(err).Fatal("Failed to create app base")
|
||||||
// run initializes and starts everything in a precise order.
|
}
|
||||||
//
|
// Other instance already running.
|
||||||
// IMPORTANT: ***Read the comments before CHANGING the order ***
|
if base == nil {
|
||||||
func run(context *cli.Context) (contextError error) { // nolint[funlen]
|
return
|
||||||
// We need to have config instance to setup a logs, panic handler, etc ...
|
}
|
||||||
cfg := config.New(appName, constants.Version, constants.Revision, cacheVersion)
|
|
||||||
|
if err := bridge.New(base).Run(os.Args); err != nil {
|
||||||
// We want to know about any problem. Our PanicHandler calls sentry which is
|
logrus.WithError(err).Fatal("Bridge exited with error")
|
||||||
// not dependent on anything else. If that fails, it tries to create crash
|
}
|
||||||
// report which will not be possible if no folder can be created. That's the
|
|
||||||
// only problem we will not be notified about in any way.
|
|
||||||
panicHandler := &cmd.PanicHandler{
|
|
||||||
AppName: "ProtonMail Bridge",
|
|
||||||
Config: cfg,
|
|
||||||
Err: &contextError,
|
|
||||||
}
|
|
||||||
defer panicHandler.HandlePanic()
|
|
||||||
|
|
||||||
// First we need config and create necessary folder; it's dependency for everything.
|
|
||||||
if err := cfg.CreateDirs(); err != nil {
|
|
||||||
log.Fatal("Cannot create necessary folders: ", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Setup of logs should be as soon as possible to ensure we record every wanted report in the log.
|
|
||||||
logLevel := context.GlobalString("log-level")
|
|
||||||
debugClient, debugServer := config.SetupLog(cfg, logLevel)
|
|
||||||
|
|
||||||
// Doesn't make sense to continue when Bridge was invoked with wrong arguments.
|
|
||||||
// We should tell that to the user before we do anything else.
|
|
||||||
if context.Args().First() != "" {
|
|
||||||
_ = cli.ShowAppHelp(context)
|
|
||||||
return cli.NewExitError("Unknown argument", 4)
|
|
||||||
}
|
|
||||||
|
|
||||||
// It's safe to get version JSON file even when other instance is running.
|
|
||||||
// (thus we put it before check of presence of other Bridge instance).
|
|
||||||
updates := updates.NewBridge(cfg.GetUpdateDir())
|
|
||||||
|
|
||||||
if dir := context.GlobalString("version-json"); dir != "" {
|
|
||||||
cmd.GenerateVersionFiles(updates, dir)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Should be called after logs are configured but before preferences are created.
|
|
||||||
migratePreferencesFromC10(cfg)
|
|
||||||
|
|
||||||
// ClearOldData before starting new bridge to do a proper setup.
|
|
||||||
//
|
|
||||||
// IMPORTANT: If you the change position of this you will need to wait
|
|
||||||
// until force-update to be applied on all currently used bridge
|
|
||||||
// versions
|
|
||||||
if err := cfg.ClearOldData(); err != nil {
|
|
||||||
log.Error("Cannot clear old data: ", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetTLSConfig is needed for IMAP, SMTL and local bridge API (to check second instance).
|
|
||||||
//
|
|
||||||
// This should be called after ClearOldData, in order to re-create the
|
|
||||||
// certificates if clean data will remove them (accidentally or on purpose).
|
|
||||||
tls, err := config.GetTLSConfig(cfg)
|
|
||||||
if err != nil {
|
|
||||||
log.WithError(err).Fatal("Cannot get TLS certificate")
|
|
||||||
}
|
|
||||||
|
|
||||||
pref := preferences.New(cfg)
|
|
||||||
|
|
||||||
// Now we can try to proceed with starting the bridge. First we need to ensure
|
|
||||||
// this is the only instance. If not, we will end and focus the existing one.
|
|
||||||
lock, err := singleinstance.CreateLockFile(cfg.GetLockPath())
|
|
||||||
if err != nil {
|
|
||||||
log.Warn("Bridge is already running")
|
|
||||||
if err := api.CheckOtherInstanceAndFocus(pref.GetInt(preferences.APIPortKey), tls); err != nil {
|
|
||||||
cmd.DisableRestart()
|
|
||||||
log.Error("Second instance: ", err)
|
|
||||||
}
|
|
||||||
return cli.NewExitError("Bridge is already running.", 3)
|
|
||||||
}
|
|
||||||
defer lock.Close() //nolint[errcheck]
|
|
||||||
|
|
||||||
// In case user wants to do CPU or memory profiles...
|
|
||||||
if doCPUProfile := context.GlobalBool("cpu-prof"); doCPUProfile {
|
|
||||||
cmd.StartCPUProfile()
|
|
||||||
defer pprof.StopCPUProfile()
|
|
||||||
}
|
|
||||||
|
|
||||||
if doMemoryProfile := context.GlobalBool("mem-prof"); doMemoryProfile {
|
|
||||||
defer cmd.MakeMemoryProfile()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now we initialize all Bridge parts.
|
|
||||||
log.Debug("Initializing bridge...")
|
|
||||||
eventListener := listener.New()
|
|
||||||
events.SetupEvents(eventListener)
|
|
||||||
|
|
||||||
credentialsStore, credentialsError := credentials.NewStore(appName)
|
|
||||||
if credentialsError != nil {
|
|
||||||
log.Error("Could not get credentials store: ", credentialsError)
|
|
||||||
}
|
|
||||||
|
|
||||||
cm := pmapi.NewClientManager(cfg.GetAPIConfig())
|
|
||||||
|
|
||||||
// Different build types have different roundtrippers (e.g. we want to enable
|
|
||||||
// TLS fingerprint checks in production builds). GetRoundTripper has a different
|
|
||||||
// implementation depending on whether build flag pmapi_prod is used or not.
|
|
||||||
cm.SetRoundTripper(cfg.GetRoundTripper(cm, eventListener))
|
|
||||||
|
|
||||||
// Cookies must be persisted across restarts.
|
|
||||||
jar, err := cookies.NewCookieJar(pref)
|
|
||||||
if err != nil {
|
|
||||||
logrus.WithError(err).Warn("Could not create cookie jar")
|
|
||||||
} else {
|
|
||||||
cm.SetCookieJar(jar)
|
|
||||||
}
|
|
||||||
|
|
||||||
bridgeInstance := bridge.New(cfg, pref, panicHandler, eventListener, cm, credentialsStore)
|
|
||||||
imapBackend := imap.NewIMAPBackend(panicHandler, eventListener, cfg, bridgeInstance)
|
|
||||||
smtpBackend := smtp.NewSMTPBackend(panicHandler, eventListener, pref, bridgeInstance)
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
defer panicHandler.HandlePanic()
|
|
||||||
apiServer := api.NewAPIServer(pref, tls, cfg.GetTLSCertPath(), cfg.GetTLSKeyPath(), eventListener)
|
|
||||||
apiServer.ListenAndServe()
|
|
||||||
}()
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
defer panicHandler.HandlePanic()
|
|
||||||
imapPort := pref.GetInt(preferences.IMAPPortKey)
|
|
||||||
imapServer := imap.NewIMAPServer(debugClient, debugServer, imapPort, tls, imapBackend, eventListener)
|
|
||||||
imapServer.ListenAndServe()
|
|
||||||
}()
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
defer panicHandler.HandlePanic()
|
|
||||||
smtpPort := pref.GetInt(preferences.SMTPPortKey)
|
|
||||||
useSSL := pref.GetBool(preferences.SMTPSSLKey)
|
|
||||||
smtpServer := smtp.NewSMTPServer(debugClient || debugServer, smtpPort, useSSL, tls, smtpBackend, eventListener)
|
|
||||||
smtpServer.ListenAndServe()
|
|
||||||
}()
|
|
||||||
|
|
||||||
// Decide about frontend mode before initializing rest of bridge.
|
|
||||||
var frontendMode string
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case context.GlobalBool("cli"):
|
|
||||||
frontendMode = "cli"
|
|
||||||
case context.GlobalBool("noninteractive"):
|
|
||||||
frontendMode = "noninteractive"
|
|
||||||
default:
|
|
||||||
frontendMode = "qt"
|
|
||||||
}
|
|
||||||
|
|
||||||
log.WithField("mode", frontendMode).Debug("Determined frontend mode to use")
|
|
||||||
|
|
||||||
// If we are starting bridge in noninteractive mode, simply block instead of starting a frontend.
|
|
||||||
if frontendMode == "noninteractive" {
|
|
||||||
<-(make(chan struct{}))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
showWindowOnStart := !context.GlobalBool("no-window")
|
|
||||||
frontend := frontend.New(constants.Version, constants.BuildVersion, frontendMode, showWindowOnStart, panicHandler, cfg, pref, eventListener, updates, bridgeInstance, smtpBackend)
|
|
||||||
|
|
||||||
// Last part is to start everything.
|
|
||||||
log.Debug("Starting frontend...")
|
|
||||||
if err := frontend.Loop(credentialsError); err != nil {
|
|
||||||
log.Error("Frontend failed with error: ", err)
|
|
||||||
return cli.NewExitError("Frontend error", 2)
|
|
||||||
}
|
|
||||||
|
|
||||||
if frontend.IsAppRestarting() {
|
|
||||||
cmd.RestartApp()
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// migratePreferencesFromC10 will copy preferences from c10 folder to c11.
|
|
||||||
// It will happen only when c10/prefs.json exists and c11/prefs.json not.
|
|
||||||
// No configuration changed between c10 and c11 versions.
|
|
||||||
func migratePreferencesFromC10(cfg *config.Config) {
|
|
||||||
pref10Path := config.New(appName, constants.Version, constants.Revision, "c10").GetPreferencesPath()
|
|
||||||
if _, err := os.Stat(pref10Path); os.IsNotExist(err) {
|
|
||||||
log.WithField("path", pref10Path).Trace("Old preferences does not exist, migration skipped")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
pref11Path := cfg.GetPreferencesPath()
|
|
||||||
if _, err := os.Stat(pref11Path); err == nil {
|
|
||||||
log.WithField("path", pref11Path).Trace("New preferences already exists, migration skipped")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
data, err := ioutil.ReadFile(pref10Path) //nolint[gosec]
|
|
||||||
if err != nil {
|
|
||||||
log.WithError(err).Error("Problem to load old preferences")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err = ioutil.WriteFile(pref11Path, data, 0600)
|
|
||||||
if err != nil {
|
|
||||||
log.WithError(err).Error("Problem to migrate preferences")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Info("Preferences migrated")
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,160 +18,40 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"runtime/pprof"
|
"os"
|
||||||
|
|
||||||
"github.com/ProtonMail/proton-bridge/internal/cmd"
|
"github.com/ProtonMail/proton-bridge/internal/app/base"
|
||||||
"github.com/ProtonMail/proton-bridge/internal/cookies"
|
"github.com/ProtonMail/proton-bridge/internal/app/ie"
|
||||||
"github.com/ProtonMail/proton-bridge/internal/events"
|
|
||||||
"github.com/ProtonMail/proton-bridge/internal/frontend"
|
|
||||||
"github.com/ProtonMail/proton-bridge/internal/importexport"
|
|
||||||
"github.com/ProtonMail/proton-bridge/internal/preferences"
|
|
||||||
"github.com/ProtonMail/proton-bridge/internal/updates"
|
|
||||||
"github.com/ProtonMail/proton-bridge/internal/users/credentials"
|
|
||||||
"github.com/ProtonMail/proton-bridge/pkg/config"
|
|
||||||
"github.com/ProtonMail/proton-bridge/pkg/constants"
|
|
||||||
"github.com/ProtonMail/proton-bridge/pkg/listener"
|
|
||||||
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
|
||||||
"github.com/allan-simon/go-singleinstance"
|
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/urfave/cli"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// cacheVersion is used for cache files such as lock, or preferences.
|
appName = "ProtonMail Import-Export app"
|
||||||
// Different number will drop old files and create new ones.
|
appUsage = "Import and export messages to/from your ProtonMail account"
|
||||||
cacheVersion = "c11"
|
configName = "importExport"
|
||||||
|
updateURLName = "ie"
|
||||||
appName = "importExport"
|
keychainName = "import-export-app"
|
||||||
appNameDash = "import-export-app"
|
cacheVersion = "c11"
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
log = logrus.WithField("pkg", "main") //nolint[gochecknoglobals]
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
cmd.Main(
|
base, err := base.New(
|
||||||
"ProtonMail Import-Export",
|
appName,
|
||||||
"ProtonMail Import-Export app",
|
appUsage,
|
||||||
nil,
|
configName,
|
||||||
run,
|
updateURLName,
|
||||||
|
keychainName,
|
||||||
|
cacheVersion,
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
// run initializes and starts everything in a precise order.
|
|
||||||
//
|
|
||||||
// IMPORTANT: ***Read the comments before CHANGING the order ***
|
|
||||||
func run(context *cli.Context) (contextError error) { // nolint[funlen]
|
|
||||||
// We need to have config instance to setup a logs, panic handler, etc ...
|
|
||||||
cfg := config.New(appName, constants.Version, constants.Revision, cacheVersion)
|
|
||||||
|
|
||||||
// We want to know about any problem. Our PanicHandler calls sentry which is
|
|
||||||
// not dependent on anything else. If that fails, it tries to create crash
|
|
||||||
// report which will not be possible if no folder can be created. That's the
|
|
||||||
// only problem we will not be notified about in any way.
|
|
||||||
panicHandler := &cmd.PanicHandler{
|
|
||||||
AppName: "ProtonMail Import-Export app",
|
|
||||||
Config: cfg,
|
|
||||||
Err: &contextError,
|
|
||||||
}
|
|
||||||
defer panicHandler.HandlePanic()
|
|
||||||
|
|
||||||
// First we need config and create necessary folder; it's dependency for everything.
|
|
||||||
if err := cfg.CreateDirs(); err != nil {
|
|
||||||
log.Fatal("Cannot create necessary folders: ", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Setup of logs should be as soon as possible to ensure we record every wanted report in the log.
|
|
||||||
logLevel := context.GlobalString("log-level")
|
|
||||||
_, _ = config.SetupLog(cfg, logLevel)
|
|
||||||
|
|
||||||
// Doesn't make sense to continue when Import-Export was invoked with wrong arguments.
|
|
||||||
// We should tell that to the user before we do anything else.
|
|
||||||
if context.Args().First() != "" {
|
|
||||||
_ = cli.ShowAppHelp(context)
|
|
||||||
return cli.NewExitError("Unknown argument", 4)
|
|
||||||
}
|
|
||||||
|
|
||||||
// It's safe to get version JSON file even when other instance is running.
|
|
||||||
// (thus we put it before check of presence of other Import-Export instance).
|
|
||||||
updates := updates.NewImportExport(cfg.GetUpdateDir())
|
|
||||||
|
|
||||||
if dir := context.GlobalString("version-json"); dir != "" {
|
|
||||||
cmd.GenerateVersionFiles(updates, dir)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now we can try to proceed with starting the Import-Export. First we need to ensure
|
|
||||||
// this is the only instance. If not, we will end and focus the existing one.
|
|
||||||
lock, err := singleinstance.CreateLockFile(cfg.GetLockPath())
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn("Import-Export app is already running")
|
logrus.WithError(err).Fatal("Failed to create app base")
|
||||||
return cli.NewExitError("Import-Export app is already running.", 3)
|
|
||||||
}
|
}
|
||||||
defer lock.Close() //nolint[errcheck]
|
// Other instance already running.
|
||||||
|
if base == nil {
|
||||||
// In case user wants to do CPU or memory profiles...
|
return
|
||||||
if doCPUProfile := context.GlobalBool("cpu-prof"); doCPUProfile {
|
|
||||||
cmd.StartCPUProfile()
|
|
||||||
defer pprof.StopCPUProfile()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if doMemoryProfile := context.GlobalBool("mem-prof"); doMemoryProfile {
|
if err := ie.New(base).Run(os.Args); err != nil {
|
||||||
defer cmd.MakeMemoryProfile()
|
logrus.WithError(err).Fatal("IE exited with error")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now we initialize all Import-Export parts.
|
|
||||||
log.Debug("Initializing import-export...")
|
|
||||||
eventListener := listener.New()
|
|
||||||
events.SetupEvents(eventListener)
|
|
||||||
|
|
||||||
credentialsStore, credentialsError := credentials.NewStore(appNameDash)
|
|
||||||
if credentialsError != nil {
|
|
||||||
log.Error("Could not get credentials store: ", credentialsError)
|
|
||||||
}
|
|
||||||
|
|
||||||
cm := pmapi.NewClientManager(cfg.GetAPIConfig())
|
|
||||||
|
|
||||||
// Different build types have different roundtrippers (e.g. we want to enable
|
|
||||||
// TLS fingerprint checks in production builds). GetRoundTripper has a different
|
|
||||||
// implementation depending on whether build flag pmapi_prod is used or not.
|
|
||||||
cm.SetRoundTripper(cfg.GetRoundTripper(cm, eventListener))
|
|
||||||
|
|
||||||
pref := preferences.New(cfg)
|
|
||||||
|
|
||||||
// Cookies must be persisted across restarts.
|
|
||||||
jar, err := cookies.NewCookieJar(pref)
|
|
||||||
if err != nil {
|
|
||||||
logrus.WithError(err).Warn("Could not create cookie jar")
|
|
||||||
} else {
|
|
||||||
cm.SetCookieJar(jar)
|
|
||||||
}
|
|
||||||
|
|
||||||
importexportInstance := importexport.New(cfg, panicHandler, eventListener, cm, credentialsStore)
|
|
||||||
|
|
||||||
// Decide about frontend mode before initializing rest of import-export.
|
|
||||||
var frontendMode string
|
|
||||||
switch {
|
|
||||||
case context.GlobalBool("cli"):
|
|
||||||
frontendMode = "cli"
|
|
||||||
default:
|
|
||||||
frontendMode = "qt"
|
|
||||||
}
|
|
||||||
log.WithField("mode", frontendMode).Debug("Determined frontend mode to use")
|
|
||||||
|
|
||||||
frontend := frontend.NewImportExport(constants.Version, constants.BuildVersion, frontendMode, panicHandler, cfg, eventListener, updates, importexportInstance)
|
|
||||||
|
|
||||||
// Last part is to start everything.
|
|
||||||
log.Debug("Starting frontend...")
|
|
||||||
if err := frontend.Loop(credentialsError); err != nil {
|
|
||||||
log.Error("Frontend failed with error: ", err)
|
|
||||||
return cli.NewExitError("Frontend error", 2)
|
|
||||||
}
|
|
||||||
|
|
||||||
if frontend.IsAppRestarting() {
|
|
||||||
cmd.RestartApp()
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
193
cmd/launcher/main.go
Normal file
193
cmd/launcher/main.go
Normal file
@ -0,0 +1,193 @@
|
|||||||
|
// 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 (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
|
"github.com/ProtonMail/gopenpgp/v2/crypto"
|
||||||
|
"github.com/ProtonMail/proton-bridge/internal/constants"
|
||||||
|
"github.com/ProtonMail/proton-bridge/internal/crash"
|
||||||
|
"github.com/ProtonMail/proton-bridge/internal/locations"
|
||||||
|
"github.com/ProtonMail/proton-bridge/internal/logging"
|
||||||
|
"github.com/ProtonMail/proton-bridge/internal/updater"
|
||||||
|
"github.com/ProtonMail/proton-bridge/internal/versioner"
|
||||||
|
"github.com/ProtonMail/proton-bridge/pkg/sentry"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
const appName = "ProtonMail Launcher"
|
||||||
|
|
||||||
|
var (
|
||||||
|
ConfigName = "" // nolint[gochecknoglobals]
|
||||||
|
ExeName = "" // nolint[gochecknoglobals]
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() { // nolint[funlen]
|
||||||
|
reporter := sentry.NewReporter(appName, constants.Version)
|
||||||
|
|
||||||
|
crashHandler := crash.NewHandler(reporter.ReportException)
|
||||||
|
defer crashHandler.HandlePanic()
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
logsPath, err := locations.ProvideLogsPath()
|
||||||
|
if err != nil {
|
||||||
|
logrus.WithError(err).Fatal("Failed to get logs path")
|
||||||
|
}
|
||||||
|
crashHandler.AddRecoveryAction(logging.DumpStackTrace(logsPath))
|
||||||
|
|
||||||
|
if err := logging.Init(logsPath); err != nil {
|
||||||
|
logrus.WithError(err).Fatal("Failed to setup logging")
|
||||||
|
}
|
||||||
|
|
||||||
|
logging.SetLevel(os.Getenv("VERBOSITY"))
|
||||||
|
|
||||||
|
updatesPath, err := locations.ProvideUpdatesPath()
|
||||||
|
if err != nil {
|
||||||
|
logrus.WithError(err).Fatal("Failed to get updates path")
|
||||||
|
}
|
||||||
|
|
||||||
|
key, err := crypto.NewKeyFromArmored(updater.DefaultPublicKey)
|
||||||
|
if err != nil {
|
||||||
|
logrus.WithError(err).Fatal("Failed to create new verification key")
|
||||||
|
}
|
||||||
|
|
||||||
|
kr, err := crypto.NewKeyRing(key)
|
||||||
|
if err != nil {
|
||||||
|
logrus.WithError(err).Fatal("Failed to create new verification keyring")
|
||||||
|
}
|
||||||
|
|
||||||
|
versioner := versioner.New(updatesPath)
|
||||||
|
|
||||||
|
exe, err := getPathToExecutable(ExeName, versioner, kr, reporter)
|
||||||
|
if err != nil {
|
||||||
|
if exe, err = getFallbackExecutable(ExeName, versioner); err != nil {
|
||||||
|
logrus.WithError(err).Fatal("Failed to find any launchable executable")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
launcher, err := os.Executable()
|
||||||
|
if err != nil {
|
||||||
|
logrus.WithError(err).Fatal("Failed to determine path to launcher")
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := exec.Command(exe, appendLauncherPath(launcher, os.Args[1:])...) // nolint[gosec]
|
||||||
|
|
||||||
|
cmd.Stdin = os.Stdin
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
|
||||||
|
// On windows, if you use Run(), a terminal stays open; we don't want that.
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
err = cmd.Start()
|
||||||
|
} else {
|
||||||
|
err = cmd.Run()
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
logrus.WithError(err).Fatal("Failed to launch")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func appendLauncherPath(path string, args []string) []string {
|
||||||
|
res := append([]string{}, args...)
|
||||||
|
|
||||||
|
hasFlag := false
|
||||||
|
|
||||||
|
for k, v := range res {
|
||||||
|
if v != "--launcher" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
hasFlag = true
|
||||||
|
|
||||||
|
if k+1 >= len(res) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
res[k+1] = path
|
||||||
|
}
|
||||||
|
|
||||||
|
if !hasFlag {
|
||||||
|
res = append(res, "--launcher", path)
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPathToExecutable(
|
||||||
|
name string,
|
||||||
|
versioner *versioner.Versioner,
|
||||||
|
kr *crypto.KeyRing,
|
||||||
|
reporter *sentry.Reporter,
|
||||||
|
) (string, error) {
|
||||||
|
versions, err := versioner.ListVersions()
|
||||||
|
if err != nil {
|
||||||
|
return "", errors.Wrap(err, "failed to list available versions")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, version := range versions {
|
||||||
|
vlog := logrus.WithField("version", version)
|
||||||
|
|
||||||
|
if err := version.VerifyFiles(kr); err != nil {
|
||||||
|
vlog.WithError(err).Error("Files failed verification and will be removed")
|
||||||
|
|
||||||
|
if err := reporter.ReportMessage(fmt.Sprintf("version %v failed verification: %v", version, err)); err != nil {
|
||||||
|
vlog.WithError(err).Error("Failed to report corrupt update files")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := version.Remove(); err != nil {
|
||||||
|
vlog.WithError(err).Error("Failed to remove files")
|
||||||
|
}
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
exe, err := version.GetExecutable(name)
|
||||||
|
if err != nil {
|
||||||
|
vlog.WithError(err).Error("Failed to get executable")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
return exe, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", errors.New("no available versions")
|
||||||
|
}
|
||||||
|
|
||||||
|
func getFallbackExecutable(name string, versioner *versioner.Versioner) (string, error) {
|
||||||
|
logrus.Info("Searching for fallback executable")
|
||||||
|
|
||||||
|
launcher, err := os.Executable()
|
||||||
|
if err != nil {
|
||||||
|
return "", errors.Wrap(err, "failed to determine path to launcher")
|
||||||
|
}
|
||||||
|
|
||||||
|
return versioner.GetExecutableInDirectory(name, filepath.Dir(launcher))
|
||||||
|
}
|
||||||
11
dist/proton-bridge.desktop
vendored
Normal file
11
dist/proton-bridge.desktop
vendored
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
[Desktop Entry]
|
||||||
|
Type=Application
|
||||||
|
Version=1.1
|
||||||
|
Name=ProtonMail Bridge
|
||||||
|
GenericName=ProtonMail Bridge for Linux
|
||||||
|
Comment=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.
|
||||||
|
Icon=protonmail-bridge
|
||||||
|
Exec=protonmail-bridge
|
||||||
|
Terminal=false
|
||||||
|
Categories=Office;Email;Network
|
||||||
|
StartupWMClass=protonmail-bridge
|
||||||
11
dist/proton-ie.desktop
vendored
Normal file
11
dist/proton-ie.desktop
vendored
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
[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
|
||||||
13
go.mod
13
go.mod
@ -14,13 +14,11 @@ require (
|
|||||||
require (
|
require (
|
||||||
github.com/0xAX/notificator v0.0.0-20191016112426-3962a5ea8da1
|
github.com/0xAX/notificator v0.0.0-20191016112426-3962a5ea8da1
|
||||||
github.com/Masterminds/semver/v3 v3.1.0
|
github.com/Masterminds/semver/v3 v3.1.0
|
||||||
github.com/ProtonMail/go-appdir v1.1.0
|
|
||||||
github.com/ProtonMail/go-apple-mobileconfig v0.0.0-20160701194735-7ea9927a11f6
|
|
||||||
github.com/ProtonMail/go-autostart v0.0.0-20181114175602-c5272053443a
|
github.com/ProtonMail/go-autostart v0.0.0-20181114175602-c5272053443a
|
||||||
github.com/ProtonMail/go-imap-id v0.0.0-20190926060100-f94a56b9ecde
|
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.5.0
|
||||||
github.com/ProtonMail/go-vcard v0.0.0-20180326232728-33aaa0a0c8a5
|
github.com/ProtonMail/go-vcard v0.0.0-20180326232728-33aaa0a0c8a5
|
||||||
github.com/ProtonMail/gopenpgp/v2 v2.0.1
|
github.com/ProtonMail/gopenpgp/v2 v2.1.3
|
||||||
github.com/PuerkitoBio/goquery v1.5.1
|
github.com/PuerkitoBio/goquery v1.5.1
|
||||||
github.com/abiosoft/ishell v2.0.0+incompatible
|
github.com/abiosoft/ishell v2.0.0+incompatible
|
||||||
github.com/abiosoft/readline v0.0.0-20180607040430-155bce2042db // indirect
|
github.com/abiosoft/readline v0.0.0-20180607040430-155bce2042db // indirect
|
||||||
@ -49,12 +47,10 @@ require (
|
|||||||
github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c // indirect
|
github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c // indirect
|
||||||
github.com/hashicorp/go-multierror v1.1.0
|
github.com/hashicorp/go-multierror v1.1.0
|
||||||
github.com/jaytaylor/html2text v0.0.0-20200412013138-3577fbdbcff7
|
github.com/jaytaylor/html2text v0.0.0-20200412013138-3577fbdbcff7
|
||||||
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0
|
|
||||||
github.com/keybase/go-keychain v0.0.0-20200502122510-cda31fe0c86d
|
github.com/keybase/go-keychain v0.0.0-20200502122510-cda31fe0c86d
|
||||||
github.com/logrusorgru/aurora v2.0.3+incompatible
|
github.com/logrusorgru/aurora v2.0.3+incompatible
|
||||||
github.com/mattn/go-runewidth v0.0.9 // indirect
|
github.com/mattn/go-runewidth v0.0.9 // indirect
|
||||||
github.com/miekg/dns v1.1.30
|
github.com/miekg/dns v1.1.30
|
||||||
github.com/myesui/uuid v1.0.0 // indirect
|
|
||||||
github.com/nsf/jsondiff v0.0.0-20200515183724-f29ed568f4ce
|
github.com/nsf/jsondiff v0.0.0-20200515183724-f29ed568f4ce
|
||||||
github.com/olekukonko/tablewriter v0.0.4 // indirect
|
github.com/olekukonko/tablewriter v0.0.4 // indirect
|
||||||
github.com/pkg/errors v0.9.1
|
github.com/pkg/errors v0.9.1
|
||||||
@ -63,17 +59,16 @@ require (
|
|||||||
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect
|
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect
|
||||||
github.com/stretchr/testify v1.6.1
|
github.com/stretchr/testify v1.6.1
|
||||||
github.com/therecipe/qt v0.0.0-20200701200531-7f61353ee73e
|
github.com/therecipe/qt v0.0.0-20200701200531-7f61353ee73e
|
||||||
github.com/twinj/uuid v1.0.0 // indirect
|
github.com/urfave/cli/v2 v2.2.0
|
||||||
github.com/urfave/cli v1.22.4
|
github.com/vmihailenco/msgpack/v5 v5.1.3
|
||||||
go.etcd.io/bbolt v1.3.5
|
go.etcd.io/bbolt v1.3.5
|
||||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381
|
golang.org/x/net v0.0.0-20200707034311-ab3426394381
|
||||||
golang.org/x/text v0.3.5-0.20201125200606-c27b9fd57aec
|
golang.org/x/text v0.3.5-0.20201125200606-c27b9fd57aec
|
||||||
gopkg.in/stretchr/testify.v1 v1.2.2 // indirect
|
|
||||||
)
|
)
|
||||||
|
|
||||||
replace (
|
replace (
|
||||||
github.com/docker/docker-credential-helpers => github.com/ProtonMail/docker-credential-helpers v1.1.0
|
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/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
|
github.com/jameskeane/bcrypt => github.com/ProtonMail/bcrypt v0.0.0-20170924085257-7509ea014998
|
||||||
golang.org/x/crypto => github.com/ProtonMail/crypto v0.0.0-20200818122824-ed5d25e28db8
|
golang.org/x/crypto => github.com/ProtonMail/crypto v0.0.0-20201112115411-41db4ea0dd1c
|
||||||
)
|
)
|
||||||
|
|||||||
54
go.sum
54
go.sum
@ -2,6 +2,7 @@ github.com/0xAX/notificator v0.0.0-20191016112426-3962a5ea8da1 h1:j9HaafapDbPbGR
|
|||||||
github.com/0xAX/notificator v0.0.0-20191016112426-3962a5ea8da1/go.mod h1:NtXa9WwQsukMHZpjNakTTz0LArxvGYdPA9CjIcUSZ6s=
|
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=
|
github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8=
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
|
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||||
github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno=
|
github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno=
|
||||||
github.com/CloudyKit/jet/v3 v3.0.0/go.mod h1:HKQPgSJmdK8hdoAbKUUWajkHyHo4RaU5rMdUywE7VMo=
|
github.com/CloudyKit/jet/v3 v3.0.0/go.mod h1:HKQPgSJmdK8hdoAbKUUWajkHyHo4RaU5rMdUywE7VMo=
|
||||||
github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY=
|
github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY=
|
||||||
@ -9,30 +10,26 @@ github.com/Masterminds/semver/v3 v3.1.0 h1:Y2lUDsFKVRSYGojLJ1yLxSXdMmMYTYls0rCvo
|
|||||||
github.com/Masterminds/semver/v3 v3.1.0/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
|
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 h1:YT2uVwQiRQZxCaaahwfcgTq2j3j66w00n/27gb/zubs=
|
||||||
github.com/ProtonMail/bcrypt v0.0.0-20170924085257-7509ea014998/go.mod h1:HecWFHognK8GfRDGnFQbW/LiV7A3MX3gZVs45vk5h8I=
|
github.com/ProtonMail/bcrypt v0.0.0-20170924085257-7509ea014998/go.mod h1:HecWFHognK8GfRDGnFQbW/LiV7A3MX3gZVs45vk5h8I=
|
||||||
github.com/ProtonMail/crypto v0.0.0-20200818122824-ed5d25e28db8 h1:u1j0xLTrCHpNS40B6m4Sv3IVUz5m9jt+AnTIopT3IgM=
|
github.com/ProtonMail/crypto v0.0.0-20201112115411-41db4ea0dd1c h1:iaVbEOnskSGgcH7XQWHG6VPirHDRoYe+Idd0/dl4m8A=
|
||||||
github.com/ProtonMail/crypto v0.0.0-20200818122824-ed5d25e28db8/go.mod h1:Pxr7w4gA2ikI4sWyYwEffm+oew1WAJHzG1SiDpQMkrI=
|
github.com/ProtonMail/crypto v0.0.0-20201112115411-41db4ea0dd1c/go.mod h1:Pxr7w4gA2ikI4sWyYwEffm+oew1WAJHzG1SiDpQMkrI=
|
||||||
github.com/ProtonMail/docker-credential-helpers v1.1.0 h1:+kvUIpwWcbtP3WFv5sSvkFn/XLzSqPOB5AAthuk9xPk=
|
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/docker-credential-helpers v1.1.0/go.mod h1:mK0aBveCxhnQ756AmaTfXMZDeULvheYVhF/MWMErN5g=
|
||||||
github.com/ProtonMail/go-appdir v1.1.0 h1:9hdNDlU9kTqRKVNzmoqah8qqrj5QZyLByQdwQNlFWig=
|
|
||||||
github.com/ProtonMail/go-appdir v1.1.0/go.mod h1:3d8Y9F5mbEUjrYbcJ3rcDxcWbqbttF+011nVZmdRdzc=
|
|
||||||
github.com/ProtonMail/go-apple-mobileconfig v0.0.0-20160701194735-7ea9927a11f6 h1:YsSJ/mvZFYydQm/hRrt8R8UtgETixN2y3LK98f5LT60=
|
|
||||||
github.com/ProtonMail/go-apple-mobileconfig v0.0.0-20160701194735-7ea9927a11f6/go.mod h1:EtDfBMIDWmVe4viZCuBTEfe3OIIo0ghbpOaAZVO+hVg=
|
|
||||||
github.com/ProtonMail/go-autostart v0.0.0-20181114175602-c5272053443a h1:fXK2KsfnkBV9Nh+9SKzHchYjuE9s0vI20JG1mbtEAcc=
|
github.com/ProtonMail/go-autostart v0.0.0-20181114175602-c5272053443a h1:fXK2KsfnkBV9Nh+9SKzHchYjuE9s0vI20JG1mbtEAcc=
|
||||||
github.com/ProtonMail/go-autostart v0.0.0-20181114175602-c5272053443a/go.mod h1:oTGdE7/DlWIr23G0IKW3OXK9wZ5Hw1GGiaJFccTvZi4=
|
github.com/ProtonMail/go-autostart v0.0.0-20181114175602-c5272053443a/go.mod h1:oTGdE7/DlWIr23G0IKW3OXK9wZ5Hw1GGiaJFccTvZi4=
|
||||||
|
github.com/ProtonMail/go-crypto v0.0.0-20201208171014-cdb7591792e2 h1:pQkjJELHayW59jp7r4G5Dlmnicr5McejDfwsjcwI1SU=
|
||||||
|
github.com/ProtonMail/go-crypto v0.0.0-20201208171014-cdb7591792e2/go.mod h1:HTM9X7e9oLwn7RiqLG0UVwVRJenLs3wN+tQ0NPAfwMQ=
|
||||||
github.com/ProtonMail/go-imap v0.0.0-20201228133358-4db68cea0cac h1:2xU3QncAiS/W3UlWZTkbNKW5WkLzk6Egl1T0xX+sbjs=
|
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 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 h1:5koQozTDELymYOyFbQ/VSubexAEXzDR8qGM5mO8GRdw=
|
||||||
github.com/ProtonMail/go-imap-id v0.0.0-20190926060100-f94a56b9ecde/go.mod h1:795VPXcRUIQ9JyMNHP4el582VokQfippgjkQP3Gk0r0=
|
github.com/ProtonMail/go-imap-id v0.0.0-20190926060100-f94a56b9ecde/go.mod h1:795VPXcRUIQ9JyMNHP4el582VokQfippgjkQP3Gk0r0=
|
||||||
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 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-mime v0.0.0-20190923161245-9b5a4261663a/go.mod h1:NYt+V3/4rEeDuaev/zw1zCq8uqVEuPHzDPo3OZrlGJ4=
|
||||||
github.com/ProtonMail/go-rfc5322 v0.4.0 h1:H6RJNNu+xdkG7A3xKU+dV9sP8/w2K4e7pz1R2FM8kd8=
|
|
||||||
github.com/ProtonMail/go-rfc5322 v0.4.0/go.mod h1:mzZWlMWnQJuYLL7JpzuPF5+FimV2lZ9f0jeq24kJjpU=
|
|
||||||
github.com/ProtonMail/go-rfc5322 v0.5.0 h1:LbKWjgfvumYZCr8BgGyTUk3ETGkFLAjQdkuSUpZ5CcE=
|
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.5.0/go.mod h1:mzZWlMWnQJuYLL7JpzuPF5+FimV2lZ9f0jeq24kJjpU=
|
||||||
github.com/ProtonMail/go-vcard v0.0.0-20180326232728-33aaa0a0c8a5 h1:Uga1DHFN4GUxuDQr0F71tpi8I9HqPIlZodZAI1lR6VQ=
|
github.com/ProtonMail/go-vcard v0.0.0-20180326232728-33aaa0a0c8a5 h1:Uga1DHFN4GUxuDQr0F71tpi8I9HqPIlZodZAI1lR6VQ=
|
||||||
github.com/ProtonMail/go-vcard v0.0.0-20180326232728-33aaa0a0c8a5/go.mod h1:oeP9CMN+ajWp5jKp1kue5daJNwMMxLF+ujPaUIoJWlA=
|
github.com/ProtonMail/go-vcard v0.0.0-20180326232728-33aaa0a0c8a5/go.mod h1:oeP9CMN+ajWp5jKp1kue5daJNwMMxLF+ujPaUIoJWlA=
|
||||||
github.com/ProtonMail/gopenpgp/v2 v2.0.1 h1:x0uvDhry5WzoHeJO4J3dgMLhG4Z9PeBJ2O+sDOY0LcU=
|
github.com/ProtonMail/gopenpgp/v2 v2.1.3 h1:4+nFDJ9WtcUQTip/je2Ll3P21XhAUl4asWsafLrw97c=
|
||||||
github.com/ProtonMail/gopenpgp/v2 v2.0.1/go.mod h1:wQQCJo7DURO6S9VwH+kSDEYs/B63yZnAEfGlOg8YNBY=
|
github.com/ProtonMail/gopenpgp/v2 v2.1.3/go.mod h1:WeYndoqEcRR4/QbgRL24z6OwYX5T1RWerRk8NfZ6rJM=
|
||||||
github.com/PuerkitoBio/goquery v1.5.1 h1:PSPBGne8NIUWw+/7vFBV+kG2J/5MOjbzc7154OaKCSE=
|
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/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
|
||||||
github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398/go.mod h1:a1uqRtAwp2Xwc6WNPJEufxJ7fx3npB4UV/JOLmbu5I0=
|
github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398/go.mod h1:a1uqRtAwp2Xwc6WNPJEufxJ7fx3npB4UV/JOLmbu5I0=
|
||||||
@ -87,8 +84,6 @@ github.com/emersion/go-imap-unselect v0.0.0-20171113212723-b985794e5f26/go.mod h
|
|||||||
github.com/emersion/go-mbox v1.0.2 h1:tE/rT+lEugK9y0myEymCCHnwlZN04hlXPrbKkxRBA5I=
|
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-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.11.1/go.mod h1:C4jnca5HOTo4bGN9YdqNQM9sITuT3Y0K6bSUw9RklvY=
|
||||||
github.com/emersion/go-message v0.12.1-0.20200903165315-e1abe21f389a h1:3C6qIGgPr1qAT0ikRD5NbyKpME/iHCDeXhpv/JJsFsE=
|
|
||||||
github.com/emersion/go-message v0.12.1-0.20200903165315-e1abe21f389a/go.mod h1:kYIioST9GDHte9/BRWgi93rpqbDuFftMjKSMaXS8ABo=
|
|
||||||
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 h1:xYuhW6egTaCP+zjbUcfoy/Dr3ASdVPR9W7fmkHvZHPE=
|
||||||
github.com/emersion/go-message v0.12.1-0.20201221184100-40c3f864532b/go.mod h1:N1JWdZQ2WRUalmdHAX308CWBq747VJ8oUorFI3VCBwU=
|
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-20191210011802-430746ea8b9b/go.mod h1:G/dpzLu16WtQpBfQ/z3LYiYJn3ZhKSGWn83fyoyQe/k=
|
||||||
@ -96,7 +91,6 @@ github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21 h1:OJyUGMJTzHTd1X
|
|||||||
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
|
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
|
||||||
github.com/emersion/go-smtp v0.14.0 h1:RYW203p+EcPjL8Z/ZpT9lZ6iOc8MG1MQzEx1UKEkXlA=
|
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-smtp v0.14.0/go.mod h1:qm27SGYgoIPRot6ubfQ/GpiPy/g3PaZAVRxiO/sDUgQ=
|
||||||
github.com/emersion/go-textwrapper v0.0.0-20160606182133-d0e65e56babe h1:40SWqY0zE3qCi6ZrtTf5OUdNm5lDnGnjRSq9GgmeTrg=
|
|
||||||
github.com/emersion/go-textwrapper v0.0.0-20160606182133-d0e65e56babe/go.mod h1:aqO8z8wPrjkscevZJFVE1wXJrLpC5LtJG7fqLOsPb2U=
|
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 h1:IbFBtwoTQyw0fIM5xv1HF+Y+3ZijDR839WMulgxCcUY=
|
||||||
github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594/go.mod h1:aqO8z8wPrjkscevZJFVE1wXJrLpC5LtJG7fqLOsPb2U=
|
github.com/emersion/go-textwrapper v0.0.0-20200911093747-65d896831594/go.mod h1:aqO8z8wPrjkscevZJFVE1wXJrLpC5LtJG7fqLOsPb2U=
|
||||||
@ -161,8 +155,6 @@ github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCV
|
|||||||
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||||
github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k=
|
github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k=
|
||||||
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 h1:iQTw/8FWTuc7uiaSepXwyf3o52HaUYcV+Tu66S3F5GA=
|
|
||||||
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
|
|
||||||
github.com/kataras/golog v0.0.10/go.mod h1:yJ8YKCmyL+nWjERB90Qwn+bdyBZsaQwU3bTVFgkFIp8=
|
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=
|
github.com/kataras/iris/v12 v12.1.8/go.mod h1:LMYy4VlP67TQ3Zgriz8RE2h2kMZV2SgMYbq3UhfoFmE=
|
||||||
github.com/kataras/neffos v0.0.14/go.mod h1:8lqADm8PnbeFfL7CLXh1WHw53dG27MC3pgi2R1rmoTE=
|
github.com/kataras/neffos v0.0.14/go.mod h1:8lqADm8PnbeFfL7CLXh1WHw53dG27MC3pgi2R1rmoTE=
|
||||||
@ -186,8 +178,8 @@ github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL
|
|||||||
github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8=
|
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/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/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||||
github.com/martinlindhe/base36 v1.0.0 h1:eYsumTah144C0A8P1T/AVSUk5ZoLnhfYFM3OGQxB52A=
|
|
||||||
github.com/martinlindhe/base36 v1.0.0/go.mod h1:+AtEs8xrBpCeYgSLoY/aJ6Wf37jtBuR0s35750M27+8=
|
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/martinlindhe/base36 v1.1.0/go.mod h1:+AtEs8xrBpCeYgSLoY/aJ6Wf37jtBuR0s35750M27+8=
|
||||||
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
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 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA=
|
||||||
@ -212,8 +204,6 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ
|
|||||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
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/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ=
|
github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ=
|
||||||
github.com/myesui/uuid v1.0.0 h1:xCBmH4l5KuvLYc5L7AS7SZg9/jKdIFubM7OVoLqaQUI=
|
|
||||||
github.com/myesui/uuid v1.0.0/go.mod h1:2CDfNgU0LR8mIdO8vdWd8i9gWWxLlcoIGGpSNgafq84=
|
|
||||||
github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg=
|
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/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/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
|
||||||
@ -272,19 +262,21 @@ github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd
|
|||||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/therecipe/qt v0.0.0-20200701200531-7f61353ee73e h1:G0DQ/TRQyrEZjtLlLwevFjaRiG8eeCMlq9WXQ2OO2bk=
|
github.com/therecipe/qt v0.0.0-20200701200531-7f61353ee73e h1:G0DQ/TRQyrEZjtLlLwevFjaRiG8eeCMlq9WXQ2OO2bk=
|
||||||
github.com/therecipe/qt v0.0.0-20200701200531-7f61353ee73e/go.mod h1:SUUR2j3aE1z6/g76SdD6NwACEpvCxb3fvG82eKbD6us=
|
github.com/therecipe/qt v0.0.0-20200701200531-7f61353ee73e/go.mod h1:SUUR2j3aE1z6/g76SdD6NwACEpvCxb3fvG82eKbD6us=
|
||||||
github.com/twinj/uuid v1.0.0 h1:fzz7COZnDrXGTAOHGuUGYd6sG+JMq+AoE7+Jlu0przk=
|
|
||||||
github.com/twinj/uuid v1.0.0/go.mod h1:mMgcE1RHFUFqe5AfiwlINXisXfDGro23fWdPUfOMjRY=
|
|
||||||
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
|
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
|
||||||
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
||||||
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
|
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
|
||||||
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
||||||
github.com/urfave/cli v1.22.4 h1:u7tSpNPPswAFymm8IehJhy4uJMlUuU/GmqSkvJ1InXA=
|
github.com/urfave/cli/v2 v2.2.0 h1:JTTnM6wKzdA0Jqodd966MVj4vWbbquZykeX1sKbe2C4=
|
||||||
github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
github.com/urfave/cli/v2 v2.2.0/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ=
|
||||||
github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4=
|
github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4=
|
||||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
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/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.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
|
||||||
github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio=
|
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=
|
||||||
|
github.com/vmihailenco/tagparser v0.1.2 h1:gnjoVuB/kljJ5wICEEOpx98oXMWPLj22G67Vbd1qPqc=
|
||||||
|
github.com/vmihailenco/tagparser v0.1.2/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI=
|
||||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
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/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/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
|
||||||
@ -295,7 +287,14 @@ github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDf
|
|||||||
github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc=
|
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 h1:XAzx9gjCb0Rxj7EoqcClPD1d5ZBxZJk0jbuoPHenBt0=
|
||||||
go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
|
go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
|
||||||
|
golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4=
|
||||||
|
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/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||||
|
golang.org/x/mobile v0.0.0-20200801112145-973feb4309de/go.mod h1:skQtrUTUwhdJvXM/2KKJzY8pDgNr9I/FOMqDVRPBUS4=
|
||||||
|
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.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-20180218175443-cbe0f9307d01/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-20180906233101-161cd47e91fd/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-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
@ -313,7 +312,6 @@ golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/
|
|||||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU=
|
golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU=
|
||||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
|
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/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 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-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
@ -332,19 +330,23 @@ golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884=
|
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884=
|
||||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/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.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
|
|
||||||
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 h1:A1qYjneJuzBZZ2gIB8rd6zrfq6l7SoEMJ8EsSilNK/U=
|
||||||
golang.org/x/text v0.3.5-0.20201125200606-c27b9fd57aec/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.5-0.20201125200606-c27b9fd57aec/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/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-20181221001348-537d06c36207/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
golang.org/x/tools v0.0.0-20190327201419-c70d86f8b7cf/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-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-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-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||||
|
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=
|
||||||
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-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 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
@ -356,8 +358,6 @@ gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8
|
|||||||
gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y=
|
gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y=
|
||||||
gopkg.in/ini.v1 v1.51.1/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/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
|
||||||
gopkg.in/stretchr/testify.v1 v1.2.2 h1:yhQC6Uy5CqibAIlk1wlusa/MJ3iAN49/BsR/dCCKz3M=
|
|
||||||
gopkg.in/stretchr/testify.v1 v1.2.2/go.mod h1:QI5V/q6UbPmuhtm10CaFZxED9NreB8PnFYN9JcR6TxU=
|
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||||
gopkg.in/yaml.v2 v2.2.2/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.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
|||||||
@ -22,14 +22,12 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/ProtonMail/proton-bridge/internal/bridge"
|
"github.com/ProtonMail/proton-bridge/internal/bridge"
|
||||||
|
"github.com/ProtonMail/proton-bridge/internal/config/settings"
|
||||||
"github.com/ProtonMail/proton-bridge/internal/events"
|
"github.com/ProtonMail/proton-bridge/internal/events"
|
||||||
"github.com/ProtonMail/proton-bridge/internal/preferences"
|
|
||||||
"github.com/ProtonMail/proton-bridge/pkg/config"
|
|
||||||
"github.com/ProtonMail/proton-bridge/pkg/listener"
|
"github.com/ProtonMail/proton-bridge/pkg/listener"
|
||||||
"github.com/ProtonMail/proton-bridge/pkg/ports"
|
"github.com/ProtonMail/proton-bridge/pkg/ports"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
@ -41,21 +39,15 @@ var (
|
|||||||
|
|
||||||
type apiServer struct {
|
type apiServer struct {
|
||||||
host string
|
host string
|
||||||
pref *config.Preferences
|
settings *settings.Settings
|
||||||
tls *tls.Config
|
|
||||||
certPath string
|
|
||||||
keyPath string
|
|
||||||
eventListener listener.Listener
|
eventListener listener.Listener
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAPIServer returns prepared API server struct.
|
// NewAPIServer returns prepared API server struct.
|
||||||
func NewAPIServer(pref *config.Preferences, tls *tls.Config, certPath, keyPath string, eventListener listener.Listener) *apiServer { //nolint[golint]
|
func NewAPIServer(settings *settings.Settings, eventListener listener.Listener) *apiServer { //nolint[golint]
|
||||||
return &apiServer{
|
return &apiServer{
|
||||||
host: bridge.Host,
|
host: bridge.Host,
|
||||||
pref: pref,
|
settings: settings,
|
||||||
tls: tls,
|
|
||||||
certPath: certPath,
|
|
||||||
keyPath: keyPath,
|
|
||||||
eventListener: eventListener,
|
eventListener: eventListener,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -67,14 +59,12 @@ func (api *apiServer) ListenAndServe() {
|
|||||||
|
|
||||||
addr := api.getAddress()
|
addr := api.getAddress()
|
||||||
server := &http.Server{
|
server := &http.Server{
|
||||||
Addr: addr,
|
Addr: addr,
|
||||||
Handler: mux,
|
Handler: mux,
|
||||||
TLSConfig: api.tls,
|
|
||||||
TLSNextProto: make(map[string]func(*http.Server, *tls.Conn, http.Handler)),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Info("API listening at ", addr)
|
log.Info("API listening at ", addr)
|
||||||
if err := server.ListenAndServeTLS(api.certPath, api.keyPath); err != nil {
|
if err := server.ListenAndServe(); err != nil {
|
||||||
api.eventListener.Emit(events.ErrorEvent, "API failed: "+err.Error())
|
api.eventListener.Emit(events.ErrorEvent, "API failed: "+err.Error())
|
||||||
log.Error("API failed: ", err)
|
log.Error("API failed: ", err)
|
||||||
}
|
}
|
||||||
@ -82,10 +72,10 @@ func (api *apiServer) ListenAndServe() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (api *apiServer) getAddress() string {
|
func (api *apiServer) getAddress() string {
|
||||||
port := api.pref.GetInt(preferences.APIPortKey)
|
port := api.settings.GetInt(settings.APIPortKey)
|
||||||
newPort := ports.FindFreePortFrom(port)
|
newPort := ports.FindFreePortFrom(port)
|
||||||
if newPort != port {
|
if newPort != port {
|
||||||
api.pref.SetInt(preferences.APIPortKey, newPort)
|
api.settings.SetInt(settings.APIPortKey, newPort)
|
||||||
}
|
}
|
||||||
return getAPIAddress(api.host, newPort)
|
return getAPIAddress(api.host, newPort)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,7 +18,6 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
@ -37,12 +36,9 @@ func focusHandler(ctx handlerContext) error {
|
|||||||
|
|
||||||
// CheckOtherInstanceAndFocus is helper for new instances to check if there is
|
// CheckOtherInstanceAndFocus is helper for new instances to check if there is
|
||||||
// already a running instance and get it's focus.
|
// already a running instance and get it's focus.
|
||||||
func CheckOtherInstanceAndFocus(port int, tls *tls.Config) error {
|
func CheckOtherInstanceAndFocus(port int) error {
|
||||||
transport := &http.Transport{TLSClientConfig: tls}
|
|
||||||
client := &http.Client{Transport: transport}
|
|
||||||
|
|
||||||
addr := getAPIAddress(bridge.Host, port)
|
addr := getAPIAddress(bridge.Host, port)
|
||||||
resp, err := client.Get("https://" + addr + "/focus")
|
resp, err := (&http.Client{}).Get("http://" + addr + "/focus")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,21 +15,21 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
package cmd
|
package base
|
||||||
|
|
||||||
import (
|
import "strings"
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// filterProcessSerialNumberFromArgs removes additional flag from MacOS. More info ProcessSerialNumber
|
// StripProcessSerialNumber removes additional flag from macOS.
|
||||||
|
// More info:
|
||||||
// http://mirror.informatimago.com/next/developer.apple.com/documentation/Carbon/Reference/Process_Manager/prmref_main/data_type_5.html#//apple_ref/doc/uid/TP30000208/C001951
|
// http://mirror.informatimago.com/next/developer.apple.com/documentation/Carbon/Reference/Process_Manager/prmref_main/data_type_5.html#//apple_ref/doc/uid/TP30000208/C001951
|
||||||
func filterProcessSerialNumberFromArgs() {
|
func StripProcessSerialNumber(args []string) []string {
|
||||||
tmp := os.Args[:0]
|
res := args[:0]
|
||||||
for _, arg := range os.Args {
|
|
||||||
|
for _, arg := range args {
|
||||||
if !strings.Contains(arg, "-psn_") {
|
if !strings.Contains(arg, "-psn_") {
|
||||||
tmp = append(tmp, arg)
|
res = append(res, arg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
os.Args = tmp
|
|
||||||
|
return res
|
||||||
}
|
}
|
||||||
366
internal/app/base/base.go
Normal file
366
internal/app/base/base.go
Normal file
@ -0,0 +1,366 @@
|
|||||||
|
// 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 base implements a common application base currently shared by bridge and IE.
|
||||||
|
// The base includes the following:
|
||||||
|
// - access to standard filesystem locations like config, cache, logging dirs
|
||||||
|
// - an extensible crash handler
|
||||||
|
// - versioned cache directory
|
||||||
|
// - persistent settings
|
||||||
|
// - event listener
|
||||||
|
// - credentials store
|
||||||
|
// - pmapi ClientManager
|
||||||
|
// In addition, the base initialises logging and reacts to command line arguments
|
||||||
|
// which control the log verbosity and enable cpu/memory profiling.
|
||||||
|
package base
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/rand"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"runtime/pprof"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Masterminds/semver/v3"
|
||||||
|
"github.com/ProtonMail/go-autostart"
|
||||||
|
"github.com/ProtonMail/gopenpgp/v2/crypto"
|
||||||
|
"github.com/ProtonMail/proton-bridge/internal/api"
|
||||||
|
"github.com/ProtonMail/proton-bridge/internal/config/cache"
|
||||||
|
"github.com/ProtonMail/proton-bridge/internal/config/settings"
|
||||||
|
"github.com/ProtonMail/proton-bridge/internal/config/tls"
|
||||||
|
"github.com/ProtonMail/proton-bridge/internal/constants"
|
||||||
|
"github.com/ProtonMail/proton-bridge/internal/cookies"
|
||||||
|
"github.com/ProtonMail/proton-bridge/internal/crash"
|
||||||
|
"github.com/ProtonMail/proton-bridge/internal/events"
|
||||||
|
"github.com/ProtonMail/proton-bridge/internal/locations"
|
||||||
|
"github.com/ProtonMail/proton-bridge/internal/logging"
|
||||||
|
"github.com/ProtonMail/proton-bridge/internal/updater"
|
||||||
|
"github.com/ProtonMail/proton-bridge/internal/users/credentials"
|
||||||
|
"github.com/ProtonMail/proton-bridge/internal/versioner"
|
||||||
|
"github.com/ProtonMail/proton-bridge/pkg/keychain"
|
||||||
|
"github.com/ProtonMail/proton-bridge/pkg/listener"
|
||||||
|
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
||||||
|
"github.com/ProtonMail/proton-bridge/pkg/sentry"
|
||||||
|
"github.com/allan-simon/go-singleinstance"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Base struct {
|
||||||
|
CrashHandler *crash.Handler
|
||||||
|
Locations *locations.Locations
|
||||||
|
Settings *settings.Settings
|
||||||
|
Lock *os.File
|
||||||
|
Cache *cache.Cache
|
||||||
|
Listener listener.Listener
|
||||||
|
Creds *credentials.Store
|
||||||
|
CM *pmapi.ClientManager
|
||||||
|
CookieJar *cookies.Jar
|
||||||
|
Updater *updater.Updater
|
||||||
|
Versioner *versioner.Versioner
|
||||||
|
TLS *tls.TLS
|
||||||
|
Autostart *autostart.App
|
||||||
|
|
||||||
|
Name string // the app's name
|
||||||
|
usage string // the app's usage description
|
||||||
|
command string // the command used to launch the app (either the exe path or the launcher path)
|
||||||
|
restart bool // whether the app is currently set to restart
|
||||||
|
|
||||||
|
teardown []func() error // actions to perform when app is exiting
|
||||||
|
}
|
||||||
|
|
||||||
|
func New( // nolint[funlen]
|
||||||
|
appName,
|
||||||
|
appUsage,
|
||||||
|
configName,
|
||||||
|
updateURLName,
|
||||||
|
keychainName,
|
||||||
|
cacheVersion string,
|
||||||
|
) (*Base, error) {
|
||||||
|
sentryReporter := sentry.NewReporter(appName, constants.Version)
|
||||||
|
crashHandler := crash.NewHandler(
|
||||||
|
sentryReporter.ReportException,
|
||||||
|
crash.ShowErrorNotification(appName),
|
||||||
|
)
|
||||||
|
defer crashHandler.HandlePanic()
|
||||||
|
|
||||||
|
rand.Seed(time.Now().UnixNano())
|
||||||
|
os.Args = StripProcessSerialNumber(os.Args)
|
||||||
|
|
||||||
|
locationsProvider, err := locations.NewDefaultProvider(filepath.Join(constants.VendorName, configName))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
locations := locations.New(locationsProvider, configName)
|
||||||
|
|
||||||
|
logsPath, err := locations.ProvideLogsPath()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := logging.Init(logsPath); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
crashHandler.AddRecoveryAction(logging.DumpStackTrace(logsPath))
|
||||||
|
|
||||||
|
if err := migrateFiles(configName); err != nil {
|
||||||
|
logrus.WithError(err).Warn("Old config files could not be migrated")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := locations.Clean(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
settingsPath, err := locations.ProvideSettingsPath()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
settingsObj := settings.New(settingsPath)
|
||||||
|
|
||||||
|
lock, err := singleinstance.CreateLockFile(locations.GetLockFile())
|
||||||
|
if err != nil {
|
||||||
|
logrus.Warnf("%v is already running", appName)
|
||||||
|
return nil, api.CheckOtherInstanceAndFocus(settingsObj.GetInt(settings.APIPortKey))
|
||||||
|
}
|
||||||
|
|
||||||
|
cachePath, err := locations.ProvideCachePath()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
cache, err := cache.New(cachePath, cacheVersion)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := cache.RemoveOldVersions(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
listener := listener.New()
|
||||||
|
events.SetupEvents(listener)
|
||||||
|
|
||||||
|
// If we can't load the keychain for whatever reason,
|
||||||
|
// we signal to frontend and supply a dummy keychain that always returns errors.
|
||||||
|
kc, err := keychain.NewKeychain(settingsObj, keychainName)
|
||||||
|
if err != nil {
|
||||||
|
listener.Emit(events.CredentialsErrorEvent, err.Error())
|
||||||
|
kc = keychain.NewMissingKeychain()
|
||||||
|
}
|
||||||
|
|
||||||
|
jar, err := cookies.NewCookieJar(settingsObj)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
apiConfig := pmapi.GetAPIConfig(configName, constants.Version)
|
||||||
|
apiConfig.ConnectionOffHandler = func() {
|
||||||
|
listener.Emit(events.InternetOffEvent, "")
|
||||||
|
}
|
||||||
|
apiConfig.ConnectionOnHandler = func() {
|
||||||
|
listener.Emit(events.InternetOnEvent, "")
|
||||||
|
}
|
||||||
|
apiConfig.UpgradeApplicationHandler = func() {
|
||||||
|
listener.Emit(events.UpgradeApplicationEvent, "")
|
||||||
|
}
|
||||||
|
cm := pmapi.NewClientManager(apiConfig)
|
||||||
|
cm.SetRoundTripper(pmapi.GetRoundTripper(cm, listener))
|
||||||
|
cm.SetCookieJar(jar)
|
||||||
|
sentryReporter.SetUserAgentProvider(cm)
|
||||||
|
|
||||||
|
key, err := crypto.NewKeyFromArmored(updater.DefaultPublicKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
kr, err := crypto.NewKeyRing(key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
updatesDir, err := locations.ProvideUpdatesPath()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
versioner := versioner.New(updatesDir)
|
||||||
|
installer := updater.NewInstaller(versioner)
|
||||||
|
updater := updater.New(
|
||||||
|
cm,
|
||||||
|
installer,
|
||||||
|
settingsObj,
|
||||||
|
kr,
|
||||||
|
semver.MustParse(constants.Version),
|
||||||
|
updateURLName,
|
||||||
|
runtime.GOOS,
|
||||||
|
)
|
||||||
|
|
||||||
|
exe, err := os.Executable()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
autostart := &autostart.App{
|
||||||
|
Name: appName,
|
||||||
|
DisplayName: appName,
|
||||||
|
Exec: []string{exe},
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Base{
|
||||||
|
CrashHandler: crashHandler,
|
||||||
|
Locations: locations,
|
||||||
|
Settings: settingsObj,
|
||||||
|
Lock: lock,
|
||||||
|
Cache: cache,
|
||||||
|
Listener: listener,
|
||||||
|
Creds: credentials.NewStore(kc),
|
||||||
|
CM: cm,
|
||||||
|
CookieJar: jar,
|
||||||
|
Updater: updater,
|
||||||
|
Versioner: versioner,
|
||||||
|
TLS: tls.New(settingsPath),
|
||||||
|
Autostart: autostart,
|
||||||
|
|
||||||
|
Name: appName,
|
||||||
|
usage: appUsage,
|
||||||
|
|
||||||
|
// By default, the command is the app's executable.
|
||||||
|
// This can be changed at runtime by using the "--launcher" flag.
|
||||||
|
command: exe,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Base) NewApp(action 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.Flags = []cli.Flag{
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "cpu-prof",
|
||||||
|
Aliases: []string{"p"},
|
||||||
|
Usage: "Generate CPU profile",
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "mem-prof",
|
||||||
|
Aliases: []string{"m"},
|
||||||
|
Usage: "Generate memory profile",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "log-level",
|
||||||
|
Aliases: []string{"l"},
|
||||||
|
Usage: "Set the log level (one of panic, fatal, error, warn, info, debug)",
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "cli",
|
||||||
|
Aliases: []string{"c"},
|
||||||
|
Usage: "Use command line interface",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "restart",
|
||||||
|
Usage: "The number of times the application has already restarted",
|
||||||
|
Hidden: true,
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "launcher",
|
||||||
|
Usage: "The launcher to use to restart the application",
|
||||||
|
Hidden: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return app
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetToRestart sets the app to restart the next time it is closed.
|
||||||
|
func (b *Base) SetToRestart() {
|
||||||
|
b.restart = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddTeardownAction adds an action to perform during app teardown.
|
||||||
|
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]
|
||||||
|
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("launcher"); launcher != "" {
|
||||||
|
b.Autostart.Exec = []string{launcher}
|
||||||
|
b.command = launcher
|
||||||
|
}
|
||||||
|
|
||||||
|
if doCPUProfile := c.Bool("cpu-prof"); doCPUProfile {
|
||||||
|
startCPUProfile()
|
||||||
|
defer pprof.StopCPUProfile()
|
||||||
|
}
|
||||||
|
|
||||||
|
if doMemoryProfile := c.Bool("mem-prof"); doMemoryProfile {
|
||||||
|
defer makeMemoryProfile()
|
||||||
|
}
|
||||||
|
|
||||||
|
logging.SetLevel(c.String("log-level"))
|
||||||
|
|
||||||
|
logrus.
|
||||||
|
WithField("appName", b.Name).
|
||||||
|
WithField("version", constants.Version).
|
||||||
|
WithField("revision", constants.Revision).
|
||||||
|
WithField("build", constants.BuildTime).
|
||||||
|
WithField("runtime", runtime.GOOS).
|
||||||
|
WithField("args", os.Args).
|
||||||
|
Info("Run app")
|
||||||
|
|
||||||
|
b.CrashHandler.AddRecoveryAction(func(interface{}) error {
|
||||||
|
if c.Int("restart") > maxAllowedRestarts {
|
||||||
|
logrus.
|
||||||
|
WithField("restart", c.Int("restart")).
|
||||||
|
Warn("Not restarting, already restarted too many times")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.restartApp(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
if err := appMainLoop(b, c); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := b.doTeardown(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.restart {
|
||||||
|
return b.restartApp(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Base) doTeardown() error {
|
||||||
|
for _, action := range b.teardown {
|
||||||
|
if err := action(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
83
internal/app/base/migration.go
Normal file
83
internal/app/base/migration.go
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
// Copyright (c) 2021 Proton Technologies AG
|
||||||
|
//
|
||||||
|
// This file is part of ProtonMail Bridge.
|
||||||
|
//
|
||||||
|
// ProtonMail Bridge is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// ProtonMail Bridge is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package base
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/ProtonMail/proton-bridge/internal/constants"
|
||||||
|
"github.com/ProtonMail/proton-bridge/internal/locations"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
// migrateFiles migrates files from their old (pre-refactor) locations to their new locations.
|
||||||
|
// We can remove this eventually.
|
||||||
|
//
|
||||||
|
// | entity | old location | new location |
|
||||||
|
// |--------|-------------------------------------------|----------------------------------------|
|
||||||
|
// | prefs | ~/.cache/protonmail/<app>/c11/prefs.json | ~/.config/protonmail/<app>/prefs.json |
|
||||||
|
// | c11 | ~/.cache/protonmail/<app>/c11 | ~/.cache/protonmail/<app>/cache/c11 |
|
||||||
|
func migrateFiles(configName string) error {
|
||||||
|
locationsProvider, err := locations.NewDefaultProvider(filepath.Join(constants.VendorName, configName))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
locations := locations.New(locationsProvider, configName)
|
||||||
|
userCacheDir := locationsProvider.UserCache()
|
||||||
|
newSettingsDir, err := locations.ProvideSettingsPath()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := moveIfExists(
|
||||||
|
filepath.Join(userCacheDir, "c11", "prefs.json"),
|
||||||
|
filepath.Join(newSettingsDir, "prefs.json"),
|
||||||
|
); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
newCacheDir, err := locations.ProvideCachePath()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := moveIfExists(
|
||||||
|
filepath.Join(userCacheDir, "c11"),
|
||||||
|
filepath.Join(newCacheDir, "c11"),
|
||||||
|
); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func moveIfExists(source, destination string) error {
|
||||||
|
if _, err := os.Stat(source); os.IsNotExist(err) {
|
||||||
|
logrus.WithField("source", source).WithField("destination", destination).Debug("No need to migrate file")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := os.Stat(destination); !os.IsNotExist(err) {
|
||||||
|
logrus.WithField("source", source).WithField("destination", destination).Debug("No need to migrate file")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return os.Rename(source, destination)
|
||||||
|
}
|
||||||
@ -15,40 +15,42 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
package cmd
|
package base
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
"runtime/pprof"
|
"runtime/pprof"
|
||||||
|
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
// StartCPUProfile starts CPU pprof.
|
// startCPUProfile starts CPU pprof.
|
||||||
func StartCPUProfile() {
|
func startCPUProfile() {
|
||||||
f, err := os.Create("./cpu.pprof")
|
f, err := os.Create("./cpu.pprof")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("Could not create CPU profile: ", err)
|
logrus.Fatal("Could not create CPU profile: ", err)
|
||||||
}
|
}
|
||||||
if err := pprof.StartCPUProfile(f); err != nil {
|
if err := pprof.StartCPUProfile(f); err != nil {
|
||||||
log.Fatal("Could not start CPU profile: ", err)
|
logrus.Fatal("Could not start CPU profile: ", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MakeMemoryProfile generates memory pprof.
|
// makeMemoryProfile generates memory pprof.
|
||||||
func MakeMemoryProfile() {
|
func makeMemoryProfile() {
|
||||||
name := "./mem.pprof"
|
name := "./mem.pprof"
|
||||||
f, err := os.Create(name)
|
f, err := os.Create(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("Could not create memory profile: ", err)
|
logrus.Fatal("Could not create memory profile: ", err)
|
||||||
}
|
}
|
||||||
if abs, err := filepath.Abs(name); err == nil {
|
if abs, err := filepath.Abs(name); err == nil {
|
||||||
name = abs
|
name = abs
|
||||||
}
|
}
|
||||||
log.Info("Writing memory profile to ", name)
|
logrus.Info("Writing memory profile to ", name)
|
||||||
runtime.GC() // get up-to-date statistics
|
runtime.GC() // get up-to-date statistics
|
||||||
if err := pprof.WriteHeapProfile(f); err != nil {
|
if err := pprof.WriteHeapProfile(f); err != nil {
|
||||||
log.Fatal("Could not write memory profile: ", err)
|
logrus.Fatal("Could not write memory profile: ", err)
|
||||||
}
|
}
|
||||||
_ = f.Close()
|
_ = f.Close()
|
||||||
}
|
}
|
||||||
79
internal/app/base/restart.go
Normal file
79
internal/app/base/restart.go
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
// 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 base
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
// maxAllowedRestarts controls after how many crashes the app will give up restarting.
|
||||||
|
const maxAllowedRestarts = 10
|
||||||
|
|
||||||
|
func (b *Base) restartApp(crash bool) error {
|
||||||
|
var args []string
|
||||||
|
|
||||||
|
if crash {
|
||||||
|
args = incrementRestartFlag(os.Args)[1:]
|
||||||
|
} else {
|
||||||
|
args = os.Args[1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
logrus.
|
||||||
|
WithField("command", b.command).
|
||||||
|
WithField("args", args).
|
||||||
|
Warn("Restarting")
|
||||||
|
|
||||||
|
return exec.Command(b.command, args...).Start() // nolint[gosec]
|
||||||
|
}
|
||||||
|
|
||||||
|
// incrementRestartFlag increments the value of the restart flag.
|
||||||
|
// If no such flag is present, it is added with initial value 1.
|
||||||
|
func incrementRestartFlag(args []string) []string {
|
||||||
|
res := append([]string{}, args...)
|
||||||
|
|
||||||
|
hasFlag := false
|
||||||
|
|
||||||
|
for k, v := range res {
|
||||||
|
if v != "--restart" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
hasFlag = true
|
||||||
|
|
||||||
|
if k+1 >= len(res) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
n, err := strconv.Atoi(res[k+1])
|
||||||
|
if err != nil {
|
||||||
|
res[k+1] = "1"
|
||||||
|
} else {
|
||||||
|
res[k+1] = strconv.Itoa(n + 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !hasFlag {
|
||||||
|
res = append(res, "--restart", "1")
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
49
internal/app/base/restart_test.go
Normal file
49
internal/app/base/restart_test.go
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
// 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 base
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestIncrementRestartFlag(t *testing.T) {
|
||||||
|
var tests = []struct {
|
||||||
|
in []string
|
||||||
|
out []string
|
||||||
|
}{
|
||||||
|
{[]string{"./bridge", "--restart", "1"}, []string{"./bridge", "--restart", "2"}},
|
||||||
|
{[]string{"./bridge", "--restart", "2"}, []string{"./bridge", "--restart", "3"}},
|
||||||
|
{[]string{"./bridge", "--other", "--restart", "2"}, []string{"./bridge", "--other", "--restart", "3"}},
|
||||||
|
{[]string{"./bridge", "--restart", "2", "--other"}, []string{"./bridge", "--restart", "3", "--other"}},
|
||||||
|
{[]string{"./bridge", "--restart", "2", "--other", "2"}, []string{"./bridge", "--restart", "3", "--other", "2"}},
|
||||||
|
{[]string{"./bridge"}, []string{"./bridge", "--restart", "1"}},
|
||||||
|
{[]string{"./bridge", "--something"}, []string{"./bridge", "--something", "--restart", "1"}},
|
||||||
|
{[]string{"./bridge", "--something", "--else"}, []string{"./bridge", "--something", "--else", "--restart", "1"}},
|
||||||
|
{[]string{"./bridge", "--restart", "bad"}, []string{"./bridge", "--restart", "1"}},
|
||||||
|
{[]string{"./bridge", "--restart", "bad", "--other"}, []string{"./bridge", "--restart", "1", "--other"}},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(strings.Join(tt.in, " "), func(t *testing.T) {
|
||||||
|
assert.Equal(t, tt.out, incrementRestartFlag(tt.in))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
226
internal/app/bridge/bridge.go
Normal file
226
internal/app/bridge/bridge.go
Normal file
@ -0,0 +1,226 @@
|
|||||||
|
// 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 bridge implements the bridge CLI application.
|
||||||
|
package bridge
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ProtonMail/proton-bridge/internal/api"
|
||||||
|
"github.com/ProtonMail/proton-bridge/internal/app/base"
|
||||||
|
"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"
|
||||||
|
"github.com/ProtonMail/proton-bridge/internal/frontend"
|
||||||
|
"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/updater"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func New(base *base.Base) *cli.App {
|
||||||
|
app := base.NewApp(run)
|
||||||
|
|
||||||
|
app.Flags = append(app.Flags, []cli.Flag{
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "log-imap",
|
||||||
|
Usage: "Enable logging of IMAP communications (all|client|server) (may contain decrypted data!)"},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "log-smtp",
|
||||||
|
Usage: "Enable logging of SMTP communications (may contain decrypted data!)"},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "no-window",
|
||||||
|
Usage: "Don't show window after start"},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "noninteractive",
|
||||||
|
Usage: "Start Bridge entirely noninteractively"},
|
||||||
|
}...)
|
||||||
|
|
||||||
|
return app
|
||||||
|
}
|
||||||
|
|
||||||
|
func run(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")
|
||||||
|
}
|
||||||
|
|
||||||
|
bridge := bridge.New(b.Locations, b.Cache, b.Settings, b.CrashHandler, b.Listener, b.CM, b.Creds, b.Updater, b.Versioner)
|
||||||
|
imapBackend := imap.NewIMAPBackend(b.CrashHandler, b.Listener, b.Cache, bridge)
|
||||||
|
smtpBackend := smtp.NewSMTPBackend(b.CrashHandler, b.Listener, b.Settings, bridge)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer b.CrashHandler.HandlePanic()
|
||||||
|
api.NewAPIServer(b.Settings, b.Listener).ListenAndServe()
|
||||||
|
}()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer b.CrashHandler.HandlePanic()
|
||||||
|
imapPort := b.Settings.GetInt(settings.IMAPPortKey)
|
||||||
|
imap.NewIMAPServer(
|
||||||
|
b.CrashHandler,
|
||||||
|
c.String("log-imap") == "client" || c.String("log-imap") == "all",
|
||||||
|
c.String("log-imap") == "server" || c.String("log-imap") == "all",
|
||||||
|
imapPort, tlsConfig, imapBackend, b.Listener).ListenAndServe()
|
||||||
|
}()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer b.CrashHandler.HandlePanic()
|
||||||
|
smtpPort := b.Settings.GetInt(settings.SMTPPortKey)
|
||||||
|
useSSL := b.Settings.GetBool(settings.SMTPSSLKey)
|
||||||
|
smtp.NewSMTPServer(
|
||||||
|
c.Bool("log-smtp"),
|
||||||
|
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, "--no-window")
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
|
||||||
|
var frontendMode string
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case c.Bool("cli"):
|
||||||
|
frontendMode = "cli"
|
||||||
|
case c.Bool("noninteractive"):
|
||||||
|
return <-(make(chan error)) // Block forever.
|
||||||
|
default:
|
||||||
|
frontendMode = "qt"
|
||||||
|
}
|
||||||
|
|
||||||
|
f := frontend.New(
|
||||||
|
constants.Version,
|
||||||
|
constants.BuildVersion,
|
||||||
|
b.Name,
|
||||||
|
frontendMode,
|
||||||
|
!c.Bool("no-window"),
|
||||||
|
b.CrashHandler,
|
||||||
|
b.Locations,
|
||||||
|
b.Settings,
|
||||||
|
b.Listener,
|
||||||
|
b.Updater,
|
||||||
|
bridge,
|
||||||
|
smtpBackend,
|
||||||
|
b.Autostart,
|
||||||
|
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 loadTLSConfig(b *base.Base) (*tls.Config, error) {
|
||||||
|
if !b.TLS.HasCerts() {
|
||||||
|
if err := generateTLSCerts(b); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tlsConfig, err := b.TLS.GetConfig()
|
||||||
|
if err == nil {
|
||||||
|
return tlsConfig, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
logrus.WithError(err).Error("Failed to load TLS config, regenerating certificates")
|
||||||
|
|
||||||
|
if err := generateTLSCerts(b); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.TLS.GetConfig()
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateTLSCerts(b *base.Base) error {
|
||||||
|
template, err := pkgTLS.NewTLSTemplate()
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "failed to generate TLS template")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := b.TLS.GenerateCerts(template); err != nil {
|
||||||
|
return errors.Wrap(err, "failed to generate TLS certs")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := b.TLS.InstallCerts(); err != nil {
|
||||||
|
return errors.Wrap(err, "failed to install TLS certs")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkAndHandleUpdate(u types.Updater, f frontend.Frontend, autoUpdate bool) {
|
||||||
|
version, err := u.Check()
|
||||||
|
if err != nil {
|
||||||
|
logrus.WithError(err).Error("An error occurred while checking for updates")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
f.WaitUntilFrontendIsReady()
|
||||||
|
|
||||||
|
// Update links in UI
|
||||||
|
f.SetVersion(version)
|
||||||
|
|
||||||
|
if !u.IsUpdateApplicable(version) {
|
||||||
|
logrus.Debug("No need to update")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
logrus.WithField("version", version.Version).Info("An update is available")
|
||||||
|
|
||||||
|
if !autoUpdate {
|
||||||
|
f.NotifyManualUpdate(version, u.CanInstall(version))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !u.CanInstall(version) {
|
||||||
|
logrus.Info("A manual update is required")
|
||||||
|
f.NotifySilentUpdateError(updater.ErrManualUpdateRequired)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := u.InstallUpdate(version); err != nil {
|
||||||
|
if errors.Cause(err) == updater.ErrDownloadVerify {
|
||||||
|
logrus.WithError(err).Warning("Skipping update installation due to temporary error")
|
||||||
|
} else {
|
||||||
|
logrus.WithError(err).Error("The update couldn't be installed")
|
||||||
|
f.NotifySilentUpdateError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
f.NotifySilentUpdateInstalled()
|
||||||
|
}
|
||||||
133
internal/app/ie/ie.go
Normal file
133
internal/app/ie/ie.go
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
// 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/ProtonMail/proton-bridge/internal/updater"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"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("cli"):
|
||||||
|
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) {
|
||||||
|
version, err := u.Check()
|
||||||
|
if err != nil {
|
||||||
|
logrus.WithError(err).Error("An error occurred while checking for updates")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
f.WaitUntilFrontendIsReady()
|
||||||
|
|
||||||
|
// Update links in UI
|
||||||
|
f.SetVersion(version)
|
||||||
|
|
||||||
|
if !u.IsUpdateApplicable(version) {
|
||||||
|
logrus.Debug("No need to update")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
logrus.WithField("version", version.Version).Info("An update is available")
|
||||||
|
|
||||||
|
if !autoUpdate {
|
||||||
|
f.NotifyManualUpdate(version, u.CanInstall(version))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !u.CanInstall(version) {
|
||||||
|
logrus.Info("A manual update is required")
|
||||||
|
f.NotifySilentUpdateError(updater.ErrManualUpdateRequired)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := u.InstallUpdate(version); err != nil {
|
||||||
|
if errors.Cause(err) == updater.ErrDownloadVerify {
|
||||||
|
logrus.WithError(err).Warning("Skipping update installation due to temporary error")
|
||||||
|
} else {
|
||||||
|
logrus.WithError(err).Error("The update couldn't be installed")
|
||||||
|
f.NotifySilentUpdateError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
f.NotifySilentUpdateInstalled()
|
||||||
|
}
|
||||||
@ -19,11 +19,14 @@
|
|||||||
package bridge
|
package bridge
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"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/metrics"
|
||||||
"github.com/ProtonMail/proton-bridge/internal/preferences"
|
"github.com/ProtonMail/proton-bridge/internal/updater"
|
||||||
"github.com/ProtonMail/proton-bridge/internal/users"
|
"github.com/ProtonMail/proton-bridge/internal/users"
|
||||||
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
||||||
|
|
||||||
@ -38,8 +41,11 @@ var (
|
|||||||
type Bridge struct {
|
type Bridge struct {
|
||||||
*users.Users
|
*users.Users
|
||||||
|
|
||||||
pref PreferenceProvider
|
locations Locator
|
||||||
|
settings SettingsProvider
|
||||||
clientManager users.ClientManager
|
clientManager users.ClientManager
|
||||||
|
updater Updater
|
||||||
|
versioner Versioner
|
||||||
|
|
||||||
userAgentClientName string
|
userAgentClientName string
|
||||||
userAgentClientVersion string
|
userAgentClientVersion string
|
||||||
@ -47,31 +53,40 @@ type Bridge struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func New(
|
func New(
|
||||||
config Configer,
|
locations Locator,
|
||||||
pref PreferenceProvider,
|
cache Cacher,
|
||||||
|
s SettingsProvider,
|
||||||
panicHandler users.PanicHandler,
|
panicHandler users.PanicHandler,
|
||||||
eventListener listener.Listener,
|
eventListener listener.Listener,
|
||||||
clientManager users.ClientManager,
|
clientManager users.ClientManager,
|
||||||
credStorer users.CredentialsStorer,
|
credStorer users.CredentialsStorer,
|
||||||
|
updater Updater,
|
||||||
|
versioner Versioner,
|
||||||
) *Bridge {
|
) *Bridge {
|
||||||
// Allow DoH before starting the app if the user has previously set this setting.
|
// Allow DoH before starting the app if the user has previously set this setting.
|
||||||
// This allows us to start even if protonmail is blocked.
|
// This allows us to start even if protonmail is blocked.
|
||||||
if pref.GetBool(preferences.AllowProxyKey) {
|
if s.GetBool(settings.AllowProxyKey) {
|
||||||
clientManager.AllowProxy()
|
clientManager.AllowProxy()
|
||||||
}
|
}
|
||||||
|
|
||||||
storeFactory := newStoreFactory(config, panicHandler, clientManager, eventListener)
|
storeFactory := newStoreFactory(cache, panicHandler, clientManager, eventListener)
|
||||||
u := users.New(config, panicHandler, eventListener, clientManager, credStorer, storeFactory, true)
|
u := users.New(locations, panicHandler, eventListener, clientManager, credStorer, storeFactory, true)
|
||||||
b := &Bridge{
|
b := &Bridge{
|
||||||
Users: u,
|
Users: u,
|
||||||
|
|
||||||
pref: pref,
|
locations: locations,
|
||||||
|
settings: s,
|
||||||
clientManager: clientManager,
|
clientManager: clientManager,
|
||||||
|
updater: updater,
|
||||||
|
versioner: versioner,
|
||||||
}
|
}
|
||||||
|
|
||||||
if pref.GetBool(preferences.FirstStartKey) {
|
if s.GetBool(settings.FirstStartKey) {
|
||||||
b.SendMetric(metrics.New(metrics.Setup, metrics.FirstStart, metrics.Label(config.GetVersion())))
|
if err := b.SendMetric(metrics.New(metrics.Setup, metrics.FirstStart, metrics.Label(constants.Version))); err != nil {
|
||||||
pref.SetBool(preferences.FirstStartKey, false)
|
logrus.WithError(err).Error("Failed to send metric")
|
||||||
|
}
|
||||||
|
|
||||||
|
s.SetBool(settings.FirstStartKey, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
go b.heartbeat()
|
go b.heartbeat()
|
||||||
@ -81,19 +96,25 @@ func New(
|
|||||||
|
|
||||||
// heartbeat sends a heartbeat signal once a day.
|
// heartbeat sends a heartbeat signal once a day.
|
||||||
func (b *Bridge) heartbeat() {
|
func (b *Bridge) heartbeat() {
|
||||||
ticker := time.NewTicker(1 * time.Minute)
|
for range time.Tick(time.Minute) {
|
||||||
|
lastHeartbeatDay, err := strconv.ParseInt(b.settings.Get(settings.LastHeartbeatKey), 10, 64)
|
||||||
for range ticker.C {
|
|
||||||
next, err := strconv.ParseInt(b.pref.Get(preferences.NextHeartbeatKey), 10, 64)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
nextTime := time.Unix(next, 0)
|
|
||||||
if time.Now().After(nextTime) {
|
// If we're still on the same day, don't send a heartbeat.
|
||||||
b.SendMetric(metrics.New(metrics.Heartbeat, metrics.Daily, metrics.NoLabel))
|
if time.Now().YearDay() == int(lastHeartbeatDay) {
|
||||||
nextTime = nextTime.Add(24 * time.Hour)
|
continue
|
||||||
b.pref.Set(preferences.NextHeartbeatKey, strconv.FormatInt(nextTime.Unix(), 10))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We're on the next (or a different) day, so send a heartbeat.
|
||||||
|
if err := b.SendMetric(metrics.New(metrics.Heartbeat, metrics.Daily, metrics.NoLabel)); err != nil {
|
||||||
|
logrus.WithError(err).Error("Failed to send heartbeat")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Heartbeat was sent successfully so update the last heartbeat day.
|
||||||
|
b.settings.Set(settings.LastHeartbeatKey, fmt.Sprintf("%v", time.Now().YearDay()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -122,6 +143,12 @@ func (b *Bridge) SetCurrentOS(os string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bridge) updateUserAgent() {
|
func (b *Bridge) updateUserAgent() {
|
||||||
|
logrus.
|
||||||
|
WithField("clientName", b.userAgentClientName).
|
||||||
|
WithField("clientVersion", b.userAgentClientVersion).
|
||||||
|
WithField("OS", b.userAgentOS).
|
||||||
|
Info("Updating user agent")
|
||||||
|
|
||||||
b.clientManager.SetUserAgent(b.userAgentClientName, b.userAgentClientVersion, b.userAgentOS)
|
b.clientManager.SetUserAgent(b.userAgentClientName, b.userAgentClientVersion, b.userAgentOS)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -150,3 +177,36 @@ func (b *Bridge) ReportBug(osType, osVersion, description, accountName, address,
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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) error {
|
||||||
|
b.settings.Set(settings.UpdateChannelKey, string(channel))
|
||||||
|
|
||||||
|
version, err := b.updater.Check()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.updater.IsDowngrade(version) {
|
||||||
|
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 {
|
||||||
|
log.WithError(err).Error("Failed to clear updates while downgrading channel")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := b.updater.InstallUpdate(version); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.versioner.RemoveOtherVersions(version.Version)
|
||||||
|
}
|
||||||
|
|||||||
@ -15,8 +15,8 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
// Code generated by ./credits.sh at Mon Dec 28 02:39:43 PM CET 2020. DO NOT EDIT.
|
// Code generated by ./credits.sh at Mon Feb 1 10:34:22 CET 2021. DO NOT EDIT.
|
||||||
|
|
||||||
package bridge
|
package bridge
|
||||||
|
|
||||||
const Credits = "github.com/0xAX/notificator;github.com/abiosoft/ishell;github.com/abiosoft/readline;github.com/allan-simon/go-singleinstance;github.com/chzyer/logex;github.com/chzyer/test;github.com/cucumber/godog;github.com/docker/docker-credential-helpers;github.com/emersion/go-imap;github.com/emersion/go-imap-appendlimit;github.com/emersion/go-imap-idle;github.com/emersion/go-imap-move;github.com/emersion/go-imap-quota;github.com/emersion/go-imap-unselect;github.com/emersion/go-mbox;github.com/emersion/go-message;github.com/emersion/go-sasl;github.com/emersion/go-smtp;github.com/emersion/go-textwrapper;github.com/emersion/go-vcard;github.com/fatih/color;github.com/flynn-archive/go-shlex;github.com/getsentry/sentry-go;github.com/golang/mock;github.com/google/go-cmp;github.com/google/uuid;github.com/gopherjs/gopherjs;github.com/go-resty/resty/v2;github.com/hashicorp/go-multierror;github.com/jameskeane/bcrypt;github.com/jaytaylor/html2text;github.com/kardianos/osext;github.com/keybase/go-keychain;github.com/logrusorgru/aurora;github.com/Masterminds/semver/v3;github.com/mattn/go-runewidth;github.com/miekg/dns;github.com/myesui/uuid;github.com/nsf/jsondiff;github.com/olekukonko/tablewriter;github.com/pkg/errors;github.com/ProtonMail/bcrypt;github.com/ProtonMail/crypto;github.com/ProtonMail/docker-credential-helpers;github.com/ProtonMail/go-appdir;github.com/ProtonMail/go-apple-mobileconfig;github.com/ProtonMail/go-autostart;github.com/ProtonMail/go-imap;github.com/ProtonMail/go-imap-id;github.com/ProtonMail/gopenpgp/v2;github.com/ProtonMail/go-rfc5322;github.com/ProtonMail/go-vcard;github.com/PuerkitoBio/goquery;github.com/sirupsen/logrus;github.com/skratchdot/open-golang;github.com/ssor/bom;github.com/stretchr/testify;github.com/therecipe/qt;github.com/twinj/uuid;github.com/urfave/cli;go.etcd.io/bbolt;golang.org/x/crypto;golang.org/x/net;golang.org/x/text;gopkg.in/stretchr/testify.v1;;Font Awesome 4.7.0;;Qt 5.13 by Qt group;"
|
const Credits = "github.com/0xAX/notificator;github.com/Masterminds/semver/v3;github.com/ProtonMail/bcrypt;github.com/ProtonMail/crypto;github.com/ProtonMail/docker-credential-helpers;github.com/ProtonMail/go-autostart;github.com/ProtonMail/go-imap;github.com/ProtonMail/go-imap-id;github.com/ProtonMail/go-rfc5322;github.com/ProtonMail/go-vcard;github.com/ProtonMail/gopenpgp/v2;github.com/PuerkitoBio/goquery;github.com/abiosoft/ishell;github.com/abiosoft/readline;github.com/allan-simon/go-singleinstance;github.com/chzyer/logex;github.com/chzyer/test;github.com/cucumber/godog;github.com/docker/docker-credential-helpers;github.com/emersion/go-imap;github.com/emersion/go-imap-appendlimit;github.com/emersion/go-imap-idle;github.com/emersion/go-imap-move;github.com/emersion/go-imap-quota;github.com/emersion/go-imap-unselect;github.com/emersion/go-mbox;github.com/emersion/go-message;github.com/emersion/go-sasl;github.com/emersion/go-smtp;github.com/emersion/go-textwrapper;github.com/emersion/go-vcard;github.com/fatih/color;github.com/flynn-archive/go-shlex;github.com/getsentry/sentry-go;github.com/go-resty/resty/v2;github.com/golang/mock;github.com/google/go-cmp;github.com/google/uuid;github.com/gopherjs/gopherjs;github.com/hashicorp/go-multierror;github.com/jameskeane/bcrypt;github.com/jaytaylor/html2text;github.com/keybase/go-keychain;github.com/logrusorgru/aurora;github.com/mattn/go-runewidth;github.com/miekg/dns;github.com/nsf/jsondiff;github.com/olekukonko/tablewriter;github.com/pkg/errors;github.com/sirupsen/logrus;github.com/skratchdot/open-golang;github.com/ssor/bom;github.com/stretchr/testify;github.com/therecipe/qt;github.com/urfave/cli/v2;github.com/vmihailenco/msgpack/v5;go.etcd.io/bbolt;golang.org/x/crypto;golang.org/x/net;golang.org/x/text;;Font Awesome 4.7.0;;Qt 5.13 by Qt group;"
|
||||||
|
|||||||
@ -28,7 +28,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type storeFactory struct {
|
type storeFactory struct {
|
||||||
config StoreFactoryConfiger
|
cache Cacher
|
||||||
panicHandler users.PanicHandler
|
panicHandler users.PanicHandler
|
||||||
clientManager users.ClientManager
|
clientManager users.ClientManager
|
||||||
eventListener listener.Listener
|
eventListener listener.Listener
|
||||||
@ -36,29 +36,29 @@ type storeFactory struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func newStoreFactory(
|
func newStoreFactory(
|
||||||
config StoreFactoryConfiger,
|
cache Cacher,
|
||||||
panicHandler users.PanicHandler,
|
panicHandler users.PanicHandler,
|
||||||
clientManager users.ClientManager,
|
clientManager users.ClientManager,
|
||||||
eventListener listener.Listener,
|
eventListener listener.Listener,
|
||||||
) *storeFactory {
|
) *storeFactory {
|
||||||
return &storeFactory{
|
return &storeFactory{
|
||||||
config: config,
|
cache: cache,
|
||||||
panicHandler: panicHandler,
|
panicHandler: panicHandler,
|
||||||
clientManager: clientManager,
|
clientManager: clientManager,
|
||||||
eventListener: eventListener,
|
eventListener: eventListener,
|
||||||
storeCache: store.NewCache(config.GetIMAPCachePath()),
|
storeCache: store.NewCache(cache.GetIMAPCachePath()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates new store for given user.
|
// New creates new store for given user.
|
||||||
func (f *storeFactory) New(user store.BridgeUser) (*store.Store, error) {
|
func (f *storeFactory) New(user store.BridgeUser) (*store.Store, error) {
|
||||||
storePath := getUserStorePath(f.config.GetDBDir(), user.ID())
|
storePath := getUserStorePath(f.cache.GetDBDir(), user.ID())
|
||||||
return store.New(f.panicHandler, user, f.clientManager, f.eventListener, storePath, f.storeCache)
|
return store.New(f.panicHandler, user, f.clientManager, f.eventListener, storePath, f.storeCache)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove removes all store files for given user.
|
// Remove removes all store files for given user.
|
||||||
func (f *storeFactory) Remove(userID string) error {
|
func (f *storeFactory) Remove(userID string) error {
|
||||||
storePath := getUserStorePath(f.config.GetDBDir(), userID)
|
storePath := getUserStorePath(f.cache.GetDBDir(), userID)
|
||||||
return store.RemoveStore(f.storeCache, storePath, userID)
|
return store.RemoveStore(f.storeCache, storePath, userID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -17,22 +17,35 @@
|
|||||||
|
|
||||||
package bridge
|
package bridge
|
||||||
|
|
||||||
import "github.com/ProtonMail/proton-bridge/internal/users"
|
import (
|
||||||
|
"github.com/Masterminds/semver/v3"
|
||||||
|
|
||||||
type Configer interface {
|
"github.com/ProtonMail/proton-bridge/internal/updater"
|
||||||
users.Configer
|
)
|
||||||
StoreFactoryConfiger
|
|
||||||
|
type Locator interface {
|
||||||
|
Clear() error
|
||||||
|
ClearUpdates() error
|
||||||
}
|
}
|
||||||
|
|
||||||
type StoreFactoryConfiger interface {
|
type Cacher interface {
|
||||||
GetDBDir() string
|
|
||||||
GetIMAPCachePath() string
|
GetIMAPCachePath() string
|
||||||
|
GetDBDir() string
|
||||||
}
|
}
|
||||||
|
|
||||||
type PreferenceProvider interface {
|
type SettingsProvider interface {
|
||||||
Get(key string) string
|
Get(key string) string
|
||||||
|
Set(key string, value string)
|
||||||
GetBool(key string) bool
|
GetBool(key string) bool
|
||||||
SetBool(key string, val bool)
|
SetBool(key string, val bool)
|
||||||
GetInt(key string) int
|
}
|
||||||
Set(key string, value string)
|
|
||||||
|
type Updater interface {
|
||||||
|
Check() (updater.VersionInfo, error)
|
||||||
|
IsDowngrade(updater.VersionInfo) bool
|
||||||
|
InstallUpdate(updater.VersionInfo) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type Versioner interface {
|
||||||
|
RemoveOtherVersions(*semver.Version) error
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,96 +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 cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"runtime"
|
|
||||||
|
|
||||||
"github.com/ProtonMail/proton-bridge/pkg/constants"
|
|
||||||
pkgSentry "github.com/ProtonMail/proton-bridge/pkg/sentry"
|
|
||||||
"github.com/getsentry/sentry-go"
|
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
"github.com/urfave/cli"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
log = logrus.WithField("pkg", "cmd") //nolint[gochecknoglobals]
|
|
||||||
|
|
||||||
baseFlags = []cli.Flag{ //nolint[gochecknoglobals]
|
|
||||||
cli.StringFlag{
|
|
||||||
Name: "log-level, l",
|
|
||||||
Usage: "Set the log level (one of panic, fatal, error, warn, info, debug, debug-client, debug-server)"},
|
|
||||||
cli.BoolFlag{
|
|
||||||
Name: "cli, c",
|
|
||||||
Usage: "Use command line interface"},
|
|
||||||
cli.StringFlag{
|
|
||||||
Name: "version-json, g",
|
|
||||||
Usage: "Generate json version file"},
|
|
||||||
cli.BoolFlag{
|
|
||||||
Name: "mem-prof, m",
|
|
||||||
Usage: "Generate memory profile"},
|
|
||||||
cli.BoolFlag{
|
|
||||||
Name: "cpu-prof, p",
|
|
||||||
Usage: "Generate CPU profile"},
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
// Main sets up Sentry, filters out unwanted args, creates app and runs it.
|
|
||||||
func Main(appName, usage string, extraFlags []cli.Flag, run func(*cli.Context) error) {
|
|
||||||
err := sentry.Init(sentry.ClientOptions{
|
|
||||||
Dsn: constants.DSNSentry,
|
|
||||||
Release: constants.Revision,
|
|
||||||
BeforeSend: pkgSentry.EnhanceSentryEvent,
|
|
||||||
})
|
|
||||||
|
|
||||||
sentry.ConfigureScope(func(scope *sentry.Scope) {
|
|
||||||
scope.SetFingerprint([]string{"{{ default }}"})
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.WithError(err).Errorln("Can not setup sentry DSN")
|
|
||||||
}
|
|
||||||
|
|
||||||
filterProcessSerialNumberFromArgs()
|
|
||||||
filterRestartNumberFromArgs()
|
|
||||||
|
|
||||||
app := newApp(appName, usage, extraFlags, run)
|
|
||||||
|
|
||||||
logrus.SetLevel(logrus.InfoLevel)
|
|
||||||
log.WithField("version", constants.Version).
|
|
||||||
WithField("revision", constants.Revision).
|
|
||||||
WithField("build", constants.BuildTime).
|
|
||||||
WithField("runtime", runtime.GOOS).
|
|
||||||
WithField("args", os.Args).
|
|
||||||
WithField("appName", app.Name).
|
|
||||||
Info("Run app")
|
|
||||||
|
|
||||||
if err := app.Run(os.Args); err != nil {
|
|
||||||
log.Error("Program exited with error: ", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func newApp(appName, usage string, extraFlags []cli.Flag, run func(*cli.Context) error) *cli.App {
|
|
||||||
app := cli.NewApp()
|
|
||||||
app.Name = appName
|
|
||||||
app.Usage = usage
|
|
||||||
app.Version = constants.BuildVersion
|
|
||||||
app.Flags = append(baseFlags, extraFlags...) //nolint[gocritic]
|
|
||||||
app.Action = run
|
|
||||||
return app
|
|
||||||
}
|
|
||||||
@ -1,111 +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 cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/ProtonMail/proton-bridge/internal/frontend"
|
|
||||||
"github.com/ProtonMail/proton-bridge/pkg/config"
|
|
||||||
"github.com/ProtonMail/proton-bridge/pkg/sentry"
|
|
||||||
"github.com/urfave/cli"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// After how many crashes app gives up starting.
|
|
||||||
maxAllowedCrashes = 10
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// How many crashes happened so far in a row.
|
|
||||||
// It will be filled from args by `filterRestartNumberFromArgs`.
|
|
||||||
// Every call of `HandlePanic` will increase this number.
|
|
||||||
// Then it will be passed as argument to the next try by `RestartApp`.
|
|
||||||
numberOfCrashes = 0 //nolint[gochecknoglobals]
|
|
||||||
)
|
|
||||||
|
|
||||||
// filterRestartNumberFromArgs removes flag with a number how many restart we already did.
|
|
||||||
// See restartApp how that number is used.
|
|
||||||
func filterRestartNumberFromArgs() {
|
|
||||||
tmp := os.Args[:0]
|
|
||||||
for i, arg := range os.Args {
|
|
||||||
if !strings.HasPrefix(arg, "--restart_") {
|
|
||||||
tmp = append(tmp, arg)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
var err error
|
|
||||||
numberOfCrashes, err = strconv.Atoi(os.Args[i][10:])
|
|
||||||
if err != nil {
|
|
||||||
numberOfCrashes = maxAllowedCrashes
|
|
||||||
}
|
|
||||||
}
|
|
||||||
os.Args = tmp
|
|
||||||
}
|
|
||||||
|
|
||||||
// DisableRestart disables restart once `RestartApp` is called.
|
|
||||||
func DisableRestart() {
|
|
||||||
numberOfCrashes = maxAllowedCrashes
|
|
||||||
}
|
|
||||||
|
|
||||||
// RestartApp starts a new instance in background.
|
|
||||||
func RestartApp() {
|
|
||||||
if numberOfCrashes >= maxAllowedCrashes {
|
|
||||||
log.Error("Too many crashes")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if exeFile, err := os.Executable(); err == nil {
|
|
||||||
arguments := append(os.Args[1:], fmt.Sprintf("--restart_%d", numberOfCrashes))
|
|
||||||
cmd := exec.Command(exeFile, arguments...) //nolint[gosec]
|
|
||||||
cmd.Stdout = os.Stdout
|
|
||||||
cmd.Stderr = os.Stderr
|
|
||||||
cmd.Stdin = os.Stdin
|
|
||||||
if err := cmd.Start(); err != nil {
|
|
||||||
log.Error("Restart failed: ", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// PanicHandler defines HandlePanic which can be used anywhere in defer.
|
|
||||||
type PanicHandler struct {
|
|
||||||
AppName string
|
|
||||||
Config *config.Config
|
|
||||||
Err *error // Pointer to error of cli action.
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandlePanic should be called in defer to ensure restart of app after error.
|
|
||||||
func (ph *PanicHandler) HandlePanic() {
|
|
||||||
sentry.SkipDuringUnwind()
|
|
||||||
|
|
||||||
r := recover()
|
|
||||||
if r == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
config.HandlePanic(ph.Config, fmt.Sprintf("Recover: %v", r))
|
|
||||||
frontend.HandlePanic(ph.AppName)
|
|
||||||
|
|
||||||
*ph.Err = cli.NewExitError("Panic and restart", 255)
|
|
||||||
numberOfCrashes++
|
|
||||||
log.Error("Restarting after panic")
|
|
||||||
RestartApp()
|
|
||||||
os.Exit(255)
|
|
||||||
}
|
|
||||||
65
internal/config/cache/cache.go
vendored
Normal file
65
internal/config/cache/cache.go
vendored
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
// 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 cache provides access to contents inside a cache directory.
|
||||||
|
package cache
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/ProtonMail/proton-bridge/pkg/files"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Cache struct {
|
||||||
|
dir, version string
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(dir, version string) (*Cache, error) {
|
||||||
|
if err := os.MkdirAll(filepath.Join(dir, version), 0700); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Cache{
|
||||||
|
dir: dir,
|
||||||
|
version: version,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDBDir returns folder for db files.
|
||||||
|
func (c *Cache) GetDBDir() string {
|
||||||
|
return c.getCurrentCacheDir()
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetIMAPCachePath returns path to file with IMAP status.
|
||||||
|
func (c *Cache) GetIMAPCachePath() string {
|
||||||
|
return filepath.Join(c.getCurrentCacheDir(), "user_info.json")
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTransferDir returns folder for import-export rules files.
|
||||||
|
func (c *Cache) GetTransferDir() string {
|
||||||
|
return c.getCurrentCacheDir()
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveOldVersions removes any cache dirs that are not the current version.
|
||||||
|
func (c *Cache) RemoveOldVersions() error {
|
||||||
|
return files.Remove(c.dir).Except(c.getCurrentCacheDir()).Do()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Cache) getCurrentCacheDir() string {
|
||||||
|
return filepath.Join(c.dir, c.version)
|
||||||
|
}
|
||||||
70
internal/config/cache/cache_test.go
vendored
Normal file
70
internal/config/cache/cache_test.go
vendored
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
// Copyright (c) 2021 Proton Technologies AG
|
||||||
|
//
|
||||||
|
// This file is part of ProtonMail Bridge.
|
||||||
|
//
|
||||||
|
// ProtonMail Bridge is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// ProtonMail Bridge is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package cache
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRemoveOldVersions(t *testing.T) {
|
||||||
|
dir, err := ioutil.TempDir("", "test-cache")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
cache, err := New(dir, "c4")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
createFilesInDir(t, dir,
|
||||||
|
"unexpected1.txt",
|
||||||
|
"c1/unexpected1.txt",
|
||||||
|
"c2/unexpected2.txt",
|
||||||
|
"c3/unexpected3.txt",
|
||||||
|
"something.txt",
|
||||||
|
)
|
||||||
|
|
||||||
|
require.DirExists(t, filepath.Join(dir, "c4"))
|
||||||
|
require.FileExists(t, filepath.Join(dir, "unexpected1.txt"))
|
||||||
|
require.FileExists(t, filepath.Join(dir, "c1", "unexpected1.txt"))
|
||||||
|
require.FileExists(t, filepath.Join(dir, "c2", "unexpected2.txt"))
|
||||||
|
require.FileExists(t, filepath.Join(dir, "c3", "unexpected3.txt"))
|
||||||
|
require.FileExists(t, filepath.Join(dir, "something.txt"))
|
||||||
|
|
||||||
|
assert.NoError(t, cache.RemoveOldVersions())
|
||||||
|
|
||||||
|
assert.DirExists(t, filepath.Join(dir, "c4"))
|
||||||
|
assert.NoFileExists(t, filepath.Join(dir, "unexpected1.txt"))
|
||||||
|
assert.NoFileExists(t, filepath.Join(dir, "c1", "unexpected1.txt"))
|
||||||
|
assert.NoFileExists(t, filepath.Join(dir, "c2", "unexpected2.txt"))
|
||||||
|
assert.NoFileExists(t, filepath.Join(dir, "c3", "unexpected3.txt"))
|
||||||
|
assert.NoFileExists(t, filepath.Join(dir, "something.txt"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func createFilesInDir(t *testing.T, dir string, files ...string) {
|
||||||
|
for _, target := range files {
|
||||||
|
require.NoError(t, os.MkdirAll(filepath.Dir(filepath.Join(dir, target)), 0700))
|
||||||
|
|
||||||
|
f, err := os.Create(filepath.Join(dir, target))
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NoError(t, f.Close())
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -15,35 +15,38 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
package config
|
package settings
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Preferences struct {
|
type keyValueStore struct {
|
||||||
cache map[string]string
|
cache map[string]string
|
||||||
path string
|
path string
|
||||||
lock *sync.RWMutex
|
lock *sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewPreferences returns loaded preferences.
|
// newKeyValueStore returns loaded preferences.
|
||||||
func NewPreferences(preferencesPath string) *Preferences {
|
func newKeyValueStore(path string) *keyValueStore {
|
||||||
p := &Preferences{
|
p := &keyValueStore{
|
||||||
path: preferencesPath,
|
path: path,
|
||||||
lock: &sync.RWMutex{},
|
lock: &sync.RWMutex{},
|
||||||
}
|
}
|
||||||
if err := p.load(); err != nil {
|
if err := p.load(); err != nil {
|
||||||
log.Warn("Cannot load preferences: ", err)
|
logrus.WithError(err).Warn("Cannot load preferences file, creating new one")
|
||||||
}
|
}
|
||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Preferences) load() error {
|
func (p *keyValueStore) load() error {
|
||||||
if p.cache != nil {
|
if p.cache != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -62,7 +65,7 @@ func (p *Preferences) load() error {
|
|||||||
return json.NewDecoder(f).Decode(&p.cache)
|
return json.NewDecoder(f).Decode(&p.cache)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Preferences) save() error {
|
func (p *keyValueStore) save() error {
|
||||||
if p.cache == nil {
|
if p.cache == nil {
|
||||||
return errors.New("cannot save preferences: cache is nil")
|
return errors.New("cannot save preferences: cache is nil")
|
||||||
}
|
}
|
||||||
@ -79,42 +82,50 @@ func (p *Preferences) save() error {
|
|||||||
return json.NewEncoder(f).Encode(p.cache)
|
return json.NewEncoder(f).Encode(p.cache)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Preferences) SetDefault(key, value string) {
|
func (p *keyValueStore) setDefault(key, value string) {
|
||||||
if p.Get(key) == "" {
|
if p.Get(key) == "" {
|
||||||
p.Set(key, value)
|
p.Set(key, value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Preferences) Get(key string) string {
|
func (p *keyValueStore) Get(key string) string {
|
||||||
p.lock.RLock()
|
p.lock.RLock()
|
||||||
defer p.lock.RUnlock()
|
defer p.lock.RUnlock()
|
||||||
|
|
||||||
return p.cache[key]
|
return p.cache[key]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Preferences) GetBool(key string) bool {
|
func (p *keyValueStore) GetBool(key string) bool {
|
||||||
return p.Get(key) == "true"
|
return p.Get(key) == "true"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Preferences) GetInt(key string) int {
|
func (p *keyValueStore) GetInt(key string) int {
|
||||||
value, err := strconv.Atoi(p.Get(key))
|
value, err := strconv.Atoi(p.Get(key))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Cannot parse int: ", err)
|
logrus.WithError(err).Error("Cannot parse int")
|
||||||
}
|
}
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Preferences) Set(key, value string) {
|
func (p *keyValueStore) GetFloat64(key string) float64 {
|
||||||
|
value, err := strconv.ParseFloat(p.Get(key), 64)
|
||||||
|
if err != nil {
|
||||||
|
logrus.WithError(err).Error("Cannot parse float64")
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *keyValueStore) Set(key, value string) {
|
||||||
p.lock.Lock()
|
p.lock.Lock()
|
||||||
p.cache[key] = value
|
p.cache[key] = value
|
||||||
p.lock.Unlock()
|
p.lock.Unlock()
|
||||||
|
|
||||||
if err := p.save(); err != nil {
|
if err := p.save(); err != nil {
|
||||||
log.Warn("Cannot save preferences: ", err)
|
logrus.WithError(err).Warn("Cannot save preferences")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Preferences) SetBool(key string, value bool) {
|
func (p *keyValueStore) SetBool(key string, value bool) {
|
||||||
if value {
|
if value {
|
||||||
p.Set(key, "true")
|
p.Set(key, "true")
|
||||||
} else {
|
} else {
|
||||||
@ -122,6 +133,10 @@ func (p *Preferences) SetBool(key string, value bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Preferences) SetInt(key string, value int) {
|
func (p *keyValueStore) SetInt(key string, value int) {
|
||||||
p.Set(key, strconv.Itoa(value))
|
p.Set(key, strconv.Itoa(value))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *keyValueStore) SetFloat64(key string, value float64) {
|
||||||
|
p.Set(key, fmt.Sprintf("%v", value))
|
||||||
|
}
|
||||||
@ -15,7 +15,7 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
package config
|
package settings
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
@ -27,82 +27,78 @@ import (
|
|||||||
|
|
||||||
const testPrefFilePath = "/tmp/pref.json"
|
const testPrefFilePath = "/tmp/pref.json"
|
||||||
|
|
||||||
func shutdownTestPreferences() {
|
func TestLoadNoKeyValueStore(t *testing.T) {
|
||||||
_ = os.RemoveAll(testPrefFilePath)
|
pref := newTestEmptyKeyValueStore(t)
|
||||||
}
|
|
||||||
|
|
||||||
func TestLoadNoPreferences(t *testing.T) {
|
|
||||||
pref := newTestEmptyPreferences(t)
|
|
||||||
require.Equal(t, "", pref.Get("key"))
|
require.Equal(t, "", pref.Get("key"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLoadBadPreferences(t *testing.T) {
|
func TestLoadBadKeyValueStore(t *testing.T) {
|
||||||
require.NoError(t, ioutil.WriteFile(testPrefFilePath, []byte("{\"key\":\"value"), 0700))
|
require.NoError(t, ioutil.WriteFile(testPrefFilePath, []byte("{\"key\":\"value"), 0700))
|
||||||
pref := NewPreferences(testPrefFilePath)
|
pref := newKeyValueStore(testPrefFilePath)
|
||||||
require.Equal(t, "", pref.Get("key"))
|
require.Equal(t, "", pref.Get("key"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPreferencesGet(t *testing.T) {
|
func TestKeyValueStoreGet(t *testing.T) {
|
||||||
pref := newTestPreferences(t)
|
pref := newTestKeyValueStore(t)
|
||||||
require.Equal(t, "value", pref.Get("str"))
|
require.Equal(t, "value", pref.Get("str"))
|
||||||
require.Equal(t, "42", pref.Get("int"))
|
require.Equal(t, "42", pref.Get("int"))
|
||||||
require.Equal(t, "true", pref.Get("bool"))
|
require.Equal(t, "true", pref.Get("bool"))
|
||||||
require.Equal(t, "t", pref.Get("falseBool"))
|
require.Equal(t, "t", pref.Get("falseBool"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPreferencesGetInt(t *testing.T) {
|
func TestKeyValueStoreGetInt(t *testing.T) {
|
||||||
pref := newTestPreferences(t)
|
pref := newTestKeyValueStore(t)
|
||||||
require.Equal(t, 0, pref.GetInt("str"))
|
require.Equal(t, 0, pref.GetInt("str"))
|
||||||
require.Equal(t, 42, pref.GetInt("int"))
|
require.Equal(t, 42, pref.GetInt("int"))
|
||||||
require.Equal(t, 0, pref.GetInt("bool"))
|
require.Equal(t, 0, pref.GetInt("bool"))
|
||||||
require.Equal(t, 0, pref.GetInt("falseBool"))
|
require.Equal(t, 0, pref.GetInt("falseBool"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPreferencesGetBool(t *testing.T) {
|
func TestKeyValueStoreGetBool(t *testing.T) {
|
||||||
pref := newTestPreferences(t)
|
pref := newTestKeyValueStore(t)
|
||||||
require.Equal(t, false, pref.GetBool("str"))
|
require.Equal(t, false, pref.GetBool("str"))
|
||||||
require.Equal(t, false, pref.GetBool("int"))
|
require.Equal(t, false, pref.GetBool("int"))
|
||||||
require.Equal(t, true, pref.GetBool("bool"))
|
require.Equal(t, true, pref.GetBool("bool"))
|
||||||
require.Equal(t, false, pref.GetBool("falseBool"))
|
require.Equal(t, false, pref.GetBool("falseBool"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPreferencesSetDefault(t *testing.T) {
|
func TestKeyValueStoreSetDefault(t *testing.T) {
|
||||||
pref := newTestEmptyPreferences(t)
|
pref := newTestEmptyKeyValueStore(t)
|
||||||
pref.SetDefault("key", "value")
|
pref.setDefault("key", "value")
|
||||||
pref.SetDefault("key", "othervalue")
|
pref.setDefault("key", "othervalue")
|
||||||
require.Equal(t, "value", pref.Get("key"))
|
require.Equal(t, "value", pref.Get("key"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPreferencesSet(t *testing.T) {
|
func TestKeyValueStoreSet(t *testing.T) {
|
||||||
pref := newTestEmptyPreferences(t)
|
pref := newTestEmptyKeyValueStore(t)
|
||||||
pref.Set("str", "value")
|
pref.Set("str", "value")
|
||||||
checkSavedPreferences(t, "{\"str\":\"value\"}")
|
checkSavedKeyValueStore(t, "{\"str\":\"value\"}")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPreferencesSetInt(t *testing.T) {
|
func TestKeyValueStoreSetInt(t *testing.T) {
|
||||||
pref := newTestEmptyPreferences(t)
|
pref := newTestEmptyKeyValueStore(t)
|
||||||
pref.SetInt("int", 42)
|
pref.SetInt("int", 42)
|
||||||
checkSavedPreferences(t, "{\"int\":\"42\"}")
|
checkSavedKeyValueStore(t, "{\"int\":\"42\"}")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPreferencesSetBool(t *testing.T) {
|
func TestKeyValueStoreSetBool(t *testing.T) {
|
||||||
pref := newTestEmptyPreferences(t)
|
pref := newTestEmptyKeyValueStore(t)
|
||||||
pref.SetBool("trueBool", true)
|
pref.SetBool("trueBool", true)
|
||||||
pref.SetBool("falseBool", false)
|
pref.SetBool("falseBool", false)
|
||||||
checkSavedPreferences(t, "{\"falseBool\":\"false\",\"trueBool\":\"true\"}")
|
checkSavedKeyValueStore(t, "{\"falseBool\":\"false\",\"trueBool\":\"true\"}")
|
||||||
}
|
}
|
||||||
|
|
||||||
func newTestEmptyPreferences(t *testing.T) *Preferences {
|
func newTestEmptyKeyValueStore(t *testing.T) *keyValueStore {
|
||||||
require.NoError(t, os.RemoveAll(testPrefFilePath))
|
require.NoError(t, os.RemoveAll(testPrefFilePath))
|
||||||
return NewPreferences(testPrefFilePath)
|
return newKeyValueStore(testPrefFilePath)
|
||||||
}
|
}
|
||||||
|
|
||||||
func newTestPreferences(t *testing.T) *Preferences {
|
func newTestKeyValueStore(t *testing.T) *keyValueStore {
|
||||||
require.NoError(t, ioutil.WriteFile(testPrefFilePath, []byte("{\"str\":\"value\",\"int\":\"42\",\"bool\":\"true\",\"falseBool\":\"t\"}"), 0700))
|
require.NoError(t, ioutil.WriteFile(testPrefFilePath, []byte("{\"str\":\"value\",\"int\":\"42\",\"bool\":\"true\",\"falseBool\":\"t\"}"), 0700))
|
||||||
return NewPreferences(testPrefFilePath)
|
return newKeyValueStore(testPrefFilePath)
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkSavedPreferences(t *testing.T, expected string) {
|
func checkSavedKeyValueStore(t *testing.T, expected string) {
|
||||||
data, err := ioutil.ReadFile(testPrefFilePath)
|
data, err := ioutil.ReadFile(testPrefFilePath)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, expected+"\n", string(data))
|
require.Equal(t, expected+"\n", string(data))
|
||||||
90
internal/config/settings/settings.go
Normal file
90
internal/config/settings/settings.go
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
// 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 settings provides access to persistent user settings.
|
||||||
|
package settings
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
|
"path/filepath"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Keys of preferences in JSON file.
|
||||||
|
const (
|
||||||
|
FirstStartKey = "first_time_start"
|
||||||
|
FirstStartGUIKey = "first_time_start_gui"
|
||||||
|
LastHeartbeatKey = "last_heartbeat"
|
||||||
|
APIPortKey = "user_port_api"
|
||||||
|
IMAPPortKey = "user_port_imap"
|
||||||
|
SMTPPortKey = "user_port_smtp"
|
||||||
|
SMTPSSLKey = "user_ssl_smtp"
|
||||||
|
AllowProxyKey = "allow_proxy"
|
||||||
|
AutostartKey = "autostart"
|
||||||
|
AutoUpdateKey = "autoupdate"
|
||||||
|
CookiesKey = "cookies"
|
||||||
|
ReportOutgoingNoEncKey = "report_outgoing_email_without_encryption"
|
||||||
|
LastVersionKey = "last_used_version"
|
||||||
|
UpdateChannelKey = "update_channel"
|
||||||
|
RolloutKey = "rollout"
|
||||||
|
PreferredKeychainKey = "preferred_keychain"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Settings struct {
|
||||||
|
*keyValueStore
|
||||||
|
|
||||||
|
settingsPath string
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(settingsPath string) *Settings {
|
||||||
|
s := &Settings{
|
||||||
|
keyValueStore: newKeyValueStore(filepath.Join(settingsPath, "prefs.json")),
|
||||||
|
settingsPath: settingsPath,
|
||||||
|
}
|
||||||
|
|
||||||
|
s.setDefaultValues()
|
||||||
|
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
DefaultIMAPPort = "1143"
|
||||||
|
DefaultSMTPPort = "1025"
|
||||||
|
DefaultAPIPort = "1042"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *Settings) setDefaultValues() {
|
||||||
|
s.setDefault(FirstStartKey, "true")
|
||||||
|
s.setDefault(FirstStartGUIKey, "true")
|
||||||
|
s.setDefault(LastHeartbeatKey, fmt.Sprintf("%v", time.Now().YearDay()))
|
||||||
|
s.setDefault(AllowProxyKey, "true")
|
||||||
|
s.setDefault(AutostartKey, "true")
|
||||||
|
s.setDefault(AutoUpdateKey, "true")
|
||||||
|
s.setDefault(ReportOutgoingNoEncKey, "false")
|
||||||
|
s.setDefault(LastVersionKey, "")
|
||||||
|
s.setDefault(UpdateChannelKey, "")
|
||||||
|
s.setDefault(RolloutKey, fmt.Sprintf("%v", rand.Float64()))
|
||||||
|
s.setDefault(PreferredKeychainKey, "")
|
||||||
|
|
||||||
|
s.setDefault(APIPortKey, DefaultAPIPort)
|
||||||
|
s.setDefault(IMAPPortKey, DefaultIMAPPort)
|
||||||
|
s.setDefault(SMTPPortKey, DefaultSMTPPort)
|
||||||
|
|
||||||
|
// By default, stick to STARTTLS. If the user uses catalina+applemail they'll have to change to SSL.
|
||||||
|
s.setDefault(SMTPSSLKey, "false")
|
||||||
|
}
|
||||||
53
internal/config/tls/cert_store_darwin.go
Normal file
53
internal/config/tls/cert_store_darwin.go
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
// 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 tls
|
||||||
|
|
||||||
|
import "os/exec"
|
||||||
|
|
||||||
|
func addTrustedCert(certPath string) error {
|
||||||
|
return exec.Command( // nolint[gosec]
|
||||||
|
"/usr/bin/security",
|
||||||
|
"execute-with-privileges",
|
||||||
|
"/usr/bin/security",
|
||||||
|
"add-trusted-cert",
|
||||||
|
"-d",
|
||||||
|
"-r", "trustRoot",
|
||||||
|
"-p", "ssl",
|
||||||
|
"-k", "/Library/Keychains/System.keychain",
|
||||||
|
certPath,
|
||||||
|
).Run()
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeTrustedCert(certPath string) error {
|
||||||
|
return exec.Command( // nolint[gosec]
|
||||||
|
"/usr/bin/security",
|
||||||
|
"execute-with-privileges",
|
||||||
|
"/usr/bin/security",
|
||||||
|
"remove-trusted-cert",
|
||||||
|
"-d",
|
||||||
|
certPath,
|
||||||
|
).Run()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TLS) InstallCerts() error {
|
||||||
|
return addTrustedCert(t.getTLSCertPath())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TLS) UninstallCerts() error {
|
||||||
|
return removeTrustedCert(t.getTLSCertPath())
|
||||||
|
}
|
||||||
26
internal/config/tls/cert_store_linux.go
Normal file
26
internal/config/tls/cert_store_linux.go
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
// Copyright (c) 2021 Proton Technologies AG
|
||||||
|
//
|
||||||
|
// This file is part of ProtonMail Bridge.
|
||||||
|
//
|
||||||
|
// ProtonMail Bridge is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// ProtonMail Bridge is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package tls
|
||||||
|
|
||||||
|
func (t *TLS) InstallCerts() error {
|
||||||
|
return nil // Linux doesn't have a root cert store.
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TLS) UninstallCerts() error {
|
||||||
|
return nil // Linux doesn't have a root cert store.
|
||||||
|
}
|
||||||
@ -15,14 +15,12 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
// Code generated by ./release-notes.sh at 'Mon Dec 28 02:39:43 PM CET 2020'. DO NOT EDIT.
|
package tls
|
||||||
|
|
||||||
package importexport
|
func (t *TLS) InstallCerts() error {
|
||||||
|
return nil // NOTE(GODT-986): Install certs to root cert store?
|
||||||
|
}
|
||||||
|
|
||||||
const ReleaseNotes = `• Allow an import of already encrypted messages (as cypher text)
|
func (t *TLS) UninstallCerts() error {
|
||||||
• Cosmetic GUI changes
|
return nil // NOTE(GODT-986): Uninstall certs from root cert store?
|
||||||
• Better error handling
|
}
|
||||||
`
|
|
||||||
|
|
||||||
const ReleaseFixedBugs = `• Installation issues on linux
|
|
||||||
`
|
|
||||||
158
internal/config/tls/tls.go
Normal file
158
internal/config/tls/tls.go
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
// 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 tls
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
|
"crypto/x509/pkix"
|
||||||
|
"encoding/pem"
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TLS struct {
|
||||||
|
settingsPath string
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(settingsPath string) *TLS {
|
||||||
|
return &TLS{
|
||||||
|
settingsPath: settingsPath,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTLSTemplate creates a new TLS template certificate with a random serial number.
|
||||||
|
func NewTLSTemplate() (*x509.Certificate, error) {
|
||||||
|
serialNumber, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128))
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "failed to generate serial number")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &x509.Certificate{
|
||||||
|
SerialNumber: serialNumber,
|
||||||
|
Subject: pkix.Name{
|
||||||
|
Country: []string{"CH"},
|
||||||
|
Organization: []string{"Proton Technologies AG"},
|
||||||
|
OrganizationalUnit: []string{"ProtonMail"},
|
||||||
|
CommonName: "127.0.0.1",
|
||||||
|
},
|
||||||
|
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
|
||||||
|
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth},
|
||||||
|
BasicConstraintsValid: true,
|
||||||
|
IsCA: true,
|
||||||
|
IPAddresses: []net.IP{net.ParseIP("127.0.0.1")},
|
||||||
|
NotBefore: time.Now(),
|
||||||
|
NotAfter: time.Now().Add(20 * 365 * 24 * time.Hour),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var ErrTLSCertExpiresSoon = fmt.Errorf("TLS certificate will expire soon")
|
||||||
|
|
||||||
|
// getTLSCertPath returns path to certificate; used for TLS servers (IMAP, SMTP).
|
||||||
|
func (t *TLS) getTLSCertPath() string {
|
||||||
|
return filepath.Join(t.settingsPath, "cert.pem")
|
||||||
|
}
|
||||||
|
|
||||||
|
// getTLSKeyPath returns path to private key; used for TLS servers (IMAP, SMTP).
|
||||||
|
func (t *TLS) getTLSKeyPath() string {
|
||||||
|
return filepath.Join(t.settingsPath, "key.pem")
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasCerts returns whether TLS certs have been generated.
|
||||||
|
func (t *TLS) HasCerts() bool {
|
||||||
|
if _, err := os.Stat(t.getTLSCertPath()); err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := os.Stat(t.getTLSKeyPath()); err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenerateCerts generates certs from the given template.
|
||||||
|
func (t *TLS) GenerateCerts(template *x509.Certificate) error {
|
||||||
|
priv, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "failed to generate private key")
|
||||||
|
}
|
||||||
|
|
||||||
|
derBytes, err := x509.CreateCertificate(rand.Reader, template, template, &priv.PublicKey, priv)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "failed to create certificate")
|
||||||
|
}
|
||||||
|
|
||||||
|
certOut, err := os.Create(t.getTLSCertPath())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer certOut.Close() // nolint[errcheck]
|
||||||
|
|
||||||
|
if err := pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
keyOut, err := os.OpenFile(t.getTLSKeyPath(), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer keyOut.Close() // nolint[errcheck]
|
||||||
|
|
||||||
|
if err := pem.Encode(keyOut, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetConfig tries to load TLS config or generate new one which is then returned.
|
||||||
|
func (t *TLS) GetConfig() (*tls.Config, error) {
|
||||||
|
c, err := tls.LoadX509KeyPair(t.getTLSCertPath(), t.getTLSKeyPath())
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "failed to load keypair")
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Leaf, err = x509.ParseCertificate(c.Certificate[0])
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "failed to parse certificate")
|
||||||
|
}
|
||||||
|
|
||||||
|
if time.Now().Add(31 * 24 * time.Hour).After(c.Leaf.NotAfter) {
|
||||||
|
return nil, ErrTLSCertExpiresSoon
|
||||||
|
}
|
||||||
|
|
||||||
|
caCertPool := x509.NewCertPool()
|
||||||
|
caCertPool.AddCert(c.Leaf)
|
||||||
|
|
||||||
|
return &tls.Config{
|
||||||
|
Certificates: []tls.Certificate{c},
|
||||||
|
ServerName: "127.0.0.1",
|
||||||
|
ClientAuth: tls.VerifyClientCertIfGiven,
|
||||||
|
RootCAs: caCertPool,
|
||||||
|
ClientCAs: caCertPool,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
77
internal/config/tls/tls_test.go
Normal file
77
internal/config/tls/tls_test.go
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
// 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 tls
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetOldConfig(t *testing.T) {
|
||||||
|
dir, err := ioutil.TempDir("", "test-tls")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Create new tls object.
|
||||||
|
tls := New(dir)
|
||||||
|
|
||||||
|
// Create new TLS template.
|
||||||
|
tlsTemplate, err := NewTLSTemplate()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Make the template be an old key.
|
||||||
|
tlsTemplate.NotBefore = time.Now().Add(-365 * 24 * time.Hour)
|
||||||
|
tlsTemplate.NotAfter = time.Now()
|
||||||
|
|
||||||
|
// Generate the certs from the template.
|
||||||
|
require.NoError(t, tls.GenerateCerts(tlsTemplate))
|
||||||
|
|
||||||
|
// Generate the config from the certs -- it's going to expire soon so we don't want to use it.
|
||||||
|
_, err = tls.GetConfig()
|
||||||
|
require.Equal(t, err, ErrTLSCertExpiresSoon)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetValidConfig(t *testing.T) {
|
||||||
|
dir, err := ioutil.TempDir("", "test-tls")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Create new tls object.
|
||||||
|
tls := New(dir)
|
||||||
|
|
||||||
|
// Create new TLS template.
|
||||||
|
tlsTemplate, err := NewTLSTemplate()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Make the template be a new key.
|
||||||
|
tlsTemplate.NotBefore = time.Now()
|
||||||
|
tlsTemplate.NotAfter = time.Now().Add(2 * 365 * 24 * time.Hour)
|
||||||
|
|
||||||
|
// Generate the certs from the template.
|
||||||
|
require.NoError(t, tls.GenerateCerts(tlsTemplate))
|
||||||
|
|
||||||
|
// Generate the config from the certs -- it's not going to expire soon so we want to use it.
|
||||||
|
config, err := tls.GetConfig()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, len(config.Certificates), 1)
|
||||||
|
|
||||||
|
// Check the cert is valid.
|
||||||
|
now, notValidAfter := time.Now(), config.Certificates[0].Leaf.NotAfter
|
||||||
|
require.False(t, now.After(notValidAfter), "new certificate expected to be valid at %v but have valid until %v", now, notValidAfter)
|
||||||
|
}
|
||||||
@ -18,6 +18,10 @@
|
|||||||
// Package constants contains variables that are set via ldflags during build.
|
// Package constants contains variables that are set via ldflags during build.
|
||||||
package constants
|
package constants
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
const VendorName = "protonmail"
|
||||||
|
|
||||||
// nolint[gochecknoglobals]
|
// nolint[gochecknoglobals]
|
||||||
var (
|
var (
|
||||||
// Version of the build.
|
// Version of the build.
|
||||||
@ -32,9 +36,6 @@ var (
|
|||||||
// DSNSentry client keys to be able to report crashes to Sentry.
|
// DSNSentry client keys to be able to report crashes to Sentry.
|
||||||
DSNSentry = ""
|
DSNSentry = ""
|
||||||
|
|
||||||
// LongVersion is derived from Version and Revision.
|
|
||||||
LongVersion = Version + " (" + Revision + ")"
|
|
||||||
|
|
||||||
// BuildVersion is derived from LongVersion and BuildTime.
|
// BuildVersion is derived from LongVersion and BuildTime.
|
||||||
BuildVersion = LongVersion + " " + BuildTime
|
BuildVersion = fmt.Sprintf("%v (%v) %v", Version, Revision, BuildTime)
|
||||||
)
|
)
|
||||||
@ -19,46 +19,41 @@
|
|||||||
package cookies
|
package cookies
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/cookiejar"
|
"net/http/cookiejar"
|
||||||
"net/url"
|
"net/url"
|
||||||
"sync"
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/ProtonMail/proton-bridge/internal/config/settings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type cookiesByHost map[string][]*http.Cookie
|
||||||
|
|
||||||
// Jar implements http.CookieJar by wrapping the standard library's cookiejar.Jar.
|
// Jar implements http.CookieJar by wrapping the standard library's cookiejar.Jar.
|
||||||
// The jar uses a pantry to load cookies at startup and save cookies when set.
|
// The jar uses a pantry to load cookies at startup and save cookies when set.
|
||||||
type Jar struct {
|
type Jar struct {
|
||||||
jar *cookiejar.Jar
|
jar *cookiejar.Jar
|
||||||
pantry *pantry
|
settings *settings.Settings
|
||||||
locker sync.Locker
|
cookies cookiesByHost
|
||||||
|
locker sync.Locker
|
||||||
}
|
}
|
||||||
|
|
||||||
type GetterSetter interface {
|
func NewCookieJar(s *settings.Settings) (*Jar, error) {
|
||||||
Get(string) string
|
|
||||||
Set(string, string)
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewCookieJar(gs GetterSetter) (*Jar, error) {
|
|
||||||
pantry := &pantry{gs: gs}
|
|
||||||
|
|
||||||
if err := pantry.discardExpiredCookies(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
cookies, err := pantry.loadFromJSON()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
jar, err := cookiejar.New(nil)
|
jar, err := cookiejar.New(nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
for rawURL, cookies := range cookies {
|
cookiesByHost, err := loadCookies(s)
|
||||||
url, err := url.Parse(rawURL)
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for host, cookies := range cookiesByHost {
|
||||||
|
url, err := url.Parse(host)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -67,9 +62,10 @@ func NewCookieJar(gs GetterSetter) (*Jar, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return &Jar{
|
return &Jar{
|
||||||
jar: jar,
|
jar: jar,
|
||||||
pantry: pantry,
|
settings: s,
|
||||||
locker: &sync.Mutex{},
|
cookies: cookiesByHost,
|
||||||
|
locker: &sync.Mutex{},
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -79,9 +75,13 @@ func (j *Jar) SetCookies(u *url.URL, cookies []*http.Cookie) {
|
|||||||
|
|
||||||
j.jar.SetCookies(u, cookies)
|
j.jar.SetCookies(u, cookies)
|
||||||
|
|
||||||
if err := j.pantry.persistCookies(u.Scheme+"://"+u.Host, cookies); err != nil {
|
for _, cookie := range cookies {
|
||||||
logrus.WithError(err).Warn("Failed to persist cookie")
|
if cookie.MaxAge > 0 {
|
||||||
|
cookie.Expires = time.Now().Add(time.Duration(cookie.MaxAge) * time.Second)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
j.cookies[fmt.Sprintf("%v://%v", u.Scheme, u.Host)] = cookies
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j *Jar) Cookies(u *url.URL) []*http.Cookie {
|
func (j *Jar) Cookies(u *url.URL) []*http.Cookie {
|
||||||
@ -90,3 +90,54 @@ func (j *Jar) Cookies(u *url.URL) []*http.Cookie {
|
|||||||
|
|
||||||
return j.jar.Cookies(u)
|
return j.jar.Cookies(u)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PersistCookies persists the cookies to disk.
|
||||||
|
func (j *Jar) PersistCookies() error {
|
||||||
|
j.locker.Lock()
|
||||||
|
defer j.locker.Unlock()
|
||||||
|
|
||||||
|
rawCookies, err := json.Marshal(j.cookies)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
j.settings.Set(settings.CookiesKey, string(rawCookies))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadCookies loads all non-expired cookies from disk.
|
||||||
|
func loadCookies(s *settings.Settings) (cookiesByHost, error) {
|
||||||
|
rawCookies := s.Get(settings.CookiesKey)
|
||||||
|
|
||||||
|
if rawCookies == "" {
|
||||||
|
return make(cookiesByHost), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var cookiesByHost cookiesByHost
|
||||||
|
|
||||||
|
if err := json.Unmarshal([]byte(rawCookies), &cookiesByHost); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for host, cookies := range cookiesByHost {
|
||||||
|
if validCookies := discardExpiredCookies(cookies); len(validCookies) > 0 {
|
||||||
|
cookiesByHost[host] = validCookies
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return cookiesByHost, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// discardExpiredCookies returns all the given cookies which aren't expired.
|
||||||
|
func discardExpiredCookies(cookies []*http.Cookie) []*http.Cookie {
|
||||||
|
var validCookies []*http.Cookie
|
||||||
|
|
||||||
|
for _, cookie := range cookies {
|
||||||
|
if cookie.Expires.After(time.Now()) {
|
||||||
|
validCookies = append(validCookies, cookie)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return validCookies
|
||||||
|
}
|
||||||
|
|||||||
@ -18,11 +18,13 @@
|
|||||||
package cookies
|
package cookies
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/ProtonMail/proton-bridge/internal/config/settings"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
@ -35,7 +37,7 @@ func TestJarGetSet(t *testing.T) {
|
|||||||
})
|
})
|
||||||
defer ts.Close()
|
defer ts.Close()
|
||||||
|
|
||||||
client := getClientWithJar(t, make(testGetterSetter))
|
client, _ := getClientWithJar(t, newFakeSettings())
|
||||||
|
|
||||||
// Hit a server that sets some cookies.
|
// Hit a server that sets some cookies.
|
||||||
setRes, err := client.Get(ts.URL + "/set")
|
setRes, err := client.Get(ts.URL + "/set")
|
||||||
@ -61,10 +63,10 @@ func TestJarLoad(t *testing.T) {
|
|||||||
defer ts.Close()
|
defer ts.Close()
|
||||||
|
|
||||||
// This will be our "persistent storage" from which the cookie jar should load cookies.
|
// This will be our "persistent storage" from which the cookie jar should load cookies.
|
||||||
gs := make(testGetterSetter)
|
s := newFakeSettings()
|
||||||
|
|
||||||
// This client saves cookies to persistent storage.
|
// This client saves cookies to persistent storage.
|
||||||
oldClient := getClientWithJar(t, gs)
|
oldClient, jar := getClientWithJar(t, s)
|
||||||
|
|
||||||
// Hit a server that sets some cookies.
|
// Hit a server that sets some cookies.
|
||||||
setRes, err := oldClient.Get(ts.URL + "/set")
|
setRes, err := oldClient.Get(ts.URL + "/set")
|
||||||
@ -73,8 +75,11 @@ func TestJarLoad(t *testing.T) {
|
|||||||
}
|
}
|
||||||
require.NoError(t, setRes.Body.Close())
|
require.NoError(t, setRes.Body.Close())
|
||||||
|
|
||||||
|
// Save the cookies.
|
||||||
|
require.NoError(t, jar.PersistCookies())
|
||||||
|
|
||||||
// This client loads cookies from persistent storage.
|
// This client loads cookies from persistent storage.
|
||||||
newClient := getClientWithJar(t, gs)
|
newClient, _ := getClientWithJar(t, s)
|
||||||
|
|
||||||
// Hit a server that checks the cookies are there.
|
// Hit a server that checks the cookies are there.
|
||||||
getRes, err := newClient.Get(ts.URL + "/get")
|
getRes, err := newClient.Get(ts.URL + "/get")
|
||||||
@ -93,10 +98,10 @@ func TestJarExpiry(t *testing.T) {
|
|||||||
defer ts.Close()
|
defer ts.Close()
|
||||||
|
|
||||||
// This will be our "persistent storage" from which the cookie jar should load cookies.
|
// This will be our "persistent storage" from which the cookie jar should load cookies.
|
||||||
gs := make(testGetterSetter)
|
s := newFakeSettings()
|
||||||
|
|
||||||
// This client saves cookies to persistent storage.
|
// This client saves cookies to persistent storage.
|
||||||
oldClient := getClientWithJar(t, gs)
|
oldClient, jar1 := getClientWithJar(t, s)
|
||||||
|
|
||||||
// Hit a server that sets some cookies.
|
// Hit a server that sets some cookies.
|
||||||
setRes, err := oldClient.Get(ts.URL + "/set")
|
setRes, err := oldClient.Get(ts.URL + "/set")
|
||||||
@ -105,15 +110,21 @@ func TestJarExpiry(t *testing.T) {
|
|||||||
}
|
}
|
||||||
require.NoError(t, setRes.Body.Close())
|
require.NoError(t, setRes.Body.Close())
|
||||||
|
|
||||||
|
// Save the cookies.
|
||||||
|
require.NoError(t, jar1.PersistCookies())
|
||||||
|
|
||||||
// Wait until the second cookie expires.
|
// Wait until the second cookie expires.
|
||||||
time.Sleep(2 * time.Second)
|
time.Sleep(2 * time.Second)
|
||||||
|
|
||||||
// Load a client, which will clear out expired cookies.
|
// Load a client, which will clear out expired cookies.
|
||||||
_ = getClientWithJar(t, gs)
|
_, jar2 := getClientWithJar(t, s)
|
||||||
|
|
||||||
assert.Contains(t, gs["cookies"], "TestName1")
|
// Save the cookies (expired ones were cleared out).
|
||||||
assert.NotContains(t, gs["cookies"], "TestName2")
|
require.NoError(t, jar2.PersistCookies())
|
||||||
assert.Contains(t, gs["cookies"], "TestName3")
|
|
||||||
|
assert.Contains(t, s.Get(settings.CookiesKey), "TestName1")
|
||||||
|
assert.NotContains(t, s.Get(settings.CookiesKey), "TestName2")
|
||||||
|
assert.Contains(t, s.Get(settings.CookiesKey), "TestName3")
|
||||||
}
|
}
|
||||||
|
|
||||||
type testCookie struct {
|
type testCookie struct {
|
||||||
@ -121,11 +132,11 @@ type testCookie struct {
|
|||||||
maxAge int
|
maxAge int
|
||||||
}
|
}
|
||||||
|
|
||||||
func getClientWithJar(t *testing.T, gs GetterSetter) *http.Client {
|
func getClientWithJar(t *testing.T, s *settings.Settings) (*http.Client, *Jar) {
|
||||||
jar, err := NewCookieJar(gs)
|
jar, err := NewCookieJar(s)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
return &http.Client{Jar: jar}
|
return &http.Client{Jar: jar}, jar
|
||||||
}
|
}
|
||||||
|
|
||||||
func getTestServer(t *testing.T, wantCookies []testCookie) *httptest.Server {
|
func getTestServer(t *testing.T, wantCookies []testCookie) *httptest.Server {
|
||||||
@ -157,12 +168,12 @@ func getTestServer(t *testing.T, wantCookies []testCookie) *httptest.Server {
|
|||||||
return httptest.NewServer(mux)
|
return httptest.NewServer(mux)
|
||||||
}
|
}
|
||||||
|
|
||||||
type testGetterSetter map[string]string
|
// newFakeSettings creates a temporary folder for files.
|
||||||
|
func newFakeSettings() *settings.Settings {
|
||||||
|
dir, err := ioutil.TempDir("", "test-settings")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
func (p testGetterSetter) Set(key, value string) {
|
return settings.New(dir)
|
||||||
p[key] = value
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p testGetterSetter) Get(key string) string {
|
|
||||||
return p[key]
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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 cookies
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"net/http"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ProtonMail/proton-bridge/internal/preferences"
|
|
||||||
)
|
|
||||||
|
|
||||||
// pantry persists and loads cookies to some persistent storage location.
|
|
||||||
type pantry struct {
|
|
||||||
gs GetterSetter
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *pantry) persistCookies(host string, cookies []*http.Cookie) error {
|
|
||||||
for _, cookie := range cookies {
|
|
||||||
if cookie.MaxAge > 0 {
|
|
||||||
cookie.Expires = time.Now().Add(time.Duration(cookie.MaxAge) * time.Second)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cookiesByHost, err := p.loadFromJSON()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
cookiesByHost[host] = cookies
|
|
||||||
|
|
||||||
return p.saveToJSON(cookiesByHost)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *pantry) discardExpiredCookies() error {
|
|
||||||
cookiesByHost, err := p.loadFromJSON()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for host, cookies := range cookiesByHost {
|
|
||||||
cookiesByHost[host] = discardExpiredCookies(cookies)
|
|
||||||
}
|
|
||||||
|
|
||||||
return p.saveToJSON(cookiesByHost)
|
|
||||||
}
|
|
||||||
|
|
||||||
type cookiesByHost map[string][]*http.Cookie
|
|
||||||
|
|
||||||
func (p *pantry) loadFromJSON() (cookiesByHost, error) {
|
|
||||||
b := p.gs.Get(preferences.CookiesKey)
|
|
||||||
|
|
||||||
if b == "" {
|
|
||||||
return make(cookiesByHost), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var cookies cookiesByHost
|
|
||||||
|
|
||||||
if err := json.Unmarshal([]byte(b), &cookies); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return cookies, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *pantry) saveToJSON(cookies cookiesByHost) error {
|
|
||||||
b, err := json.Marshal(cookies)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
p.gs.Set(preferences.CookiesKey, string(b))
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func discardExpiredCookies(cookies []*http.Cookie) (validCookies []*http.Cookie) {
|
|
||||||
for _, cookie := range cookies {
|
|
||||||
if cookie.Expires.After(time.Now()) {
|
|
||||||
validCookies = append(validCookies, cookie)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
42
internal/crash/actions.go
Normal file
42
internal/crash/actions.go
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
// Copyright (c) 2021 Proton Technologies AG
|
||||||
|
//
|
||||||
|
// This file is part of ProtonMail Bridge.
|
||||||
|
//
|
||||||
|
// ProtonMail Bridge is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// ProtonMail Bridge is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package crash
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/0xAX/notificator"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ShowErrorNotification shows a system notification that the app with the given appName has crashed.
|
||||||
|
// NOTE: Icons shouldn't be hardcoded.
|
||||||
|
func ShowErrorNotification(appName string) RecoveryAction {
|
||||||
|
return func(r interface{}) error {
|
||||||
|
notify := notificator.New(notificator.Options{
|
||||||
|
DefaultIcon: "../frontend/ui/icon/icon.png",
|
||||||
|
AppName: appName,
|
||||||
|
})
|
||||||
|
|
||||||
|
return notify.Push(
|
||||||
|
"Fatal Error",
|
||||||
|
fmt.Sprintf("%v has encountered a fatal error.", appName),
|
||||||
|
"/frontend/icon/icon.png",
|
||||||
|
notificator.UR_CRITICAL,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -15,36 +15,40 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
// +build build_qa
|
// Package crash implements a crash handler with configurable recovery actions.
|
||||||
|
package crash
|
||||||
package config
|
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/ProtonMail/proton-bridge/pkg/sentry"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
// getLogLevelAndFile for QA build is altered in a way even decrypted data are stored
|
type RecoveryAction func(interface{}) error
|
||||||
// in the log file when forced with `debug-client-json` or `debug-server-json`.
|
|
||||||
func getLogLevelAndFile(levelFlag string) (level logrus.Level, useFile bool) {
|
type Handler struct {
|
||||||
useFile = true
|
actions []RecoveryAction
|
||||||
switch levelFlag {
|
}
|
||||||
case "panic":
|
|
||||||
level = logrus.PanicLevel
|
func NewHandler(actions ...RecoveryAction) *Handler {
|
||||||
case "fatal":
|
return &Handler{actions: actions}
|
||||||
level = logrus.FatalLevel
|
}
|
||||||
case "error":
|
|
||||||
level = logrus.ErrorLevel
|
func (h *Handler) AddRecoveryAction(action RecoveryAction) *Handler {
|
||||||
case "warn":
|
h.actions = append(h.actions, action)
|
||||||
level = logrus.WarnLevel
|
return h
|
||||||
case "info":
|
}
|
||||||
level = logrus.InfoLevel
|
|
||||||
case "debug-client-json", "debug-server-json":
|
func (h *Handler) HandlePanic() {
|
||||||
level = logrus.DebugLevel
|
sentry.SkipDuringUnwind()
|
||||||
case "debug", "debug-client", "debug-server":
|
|
||||||
level = logrus.DebugLevel
|
r := recover()
|
||||||
useFile = false
|
if r == nil {
|
||||||
default:
|
return
|
||||||
level = logrus.InfoLevel
|
}
|
||||||
}
|
|
||||||
return
|
for _, action := range h.actions {
|
||||||
|
if err := action(r); err != nil {
|
||||||
|
logrus.WithError(err).Error("Failed to execute recovery action")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -15,29 +15,44 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
// +build !pmapi_prod
|
package crash
|
||||||
|
|
||||||
package config
|
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"fmt"
|
||||||
"strings"
|
"testing"
|
||||||
|
|
||||||
"github.com/ProtonMail/proton-bridge/pkg/listener"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (c *Config) GetAPIConfig() *pmapi.ClientConfig {
|
func TestHandler(t *testing.T) {
|
||||||
return &pmapi.ClientConfig{
|
var s string
|
||||||
AppVersion: c.getAPIOS() + strings.Title(c.appName) + "_" + c.version,
|
|
||||||
ClientID: c.appName,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func SetClientRoundTripper(_ *pmapi.ClientManager, _ *pmapi.ClientConfig, _ listener.Listener) {
|
h := NewHandler(
|
||||||
// Use the default roundtripper; do nothing.
|
func(r interface{}) error {
|
||||||
}
|
s += fmt.Sprintf("1: %v\n", r)
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
func(r interface{}) error {
|
||||||
|
s += fmt.Sprintf("2: %v\n", r)
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
func (c *Config) GetRoundTripper(_ *pmapi.ClientManager, _ listener.Listener) http.RoundTripper {
|
h.
|
||||||
return http.DefaultTransport
|
AddRecoveryAction(func(r interface{}) error {
|
||||||
|
s += fmt.Sprintf("3: %v\n", r)
|
||||||
|
return nil
|
||||||
|
}).
|
||||||
|
AddRecoveryAction(func(r interface{}) error {
|
||||||
|
s += fmt.Sprintf("4: %v\n", r)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
assert.Equal(t, "1: thing\n2: thing\n3: thing\n4: thing\n", s)
|
||||||
|
}()
|
||||||
|
|
||||||
|
defer h.HandlePanic()
|
||||||
|
|
||||||
|
panic("thing")
|
||||||
}
|
}
|
||||||
@ -27,6 +27,7 @@ import (
|
|||||||
// Constants of events used by the event listener in bridge.
|
// Constants of events used by the event listener in bridge.
|
||||||
const (
|
const (
|
||||||
ErrorEvent = "error"
|
ErrorEvent = "error"
|
||||||
|
CredentialsErrorEvent = "credentialsError"
|
||||||
CloseConnectionEvent = "closeConnection"
|
CloseConnectionEvent = "closeConnection"
|
||||||
LogoutEvent = "logout"
|
LogoutEvent = "logout"
|
||||||
AddressChangedEvent = "addressChanged"
|
AddressChangedEvent = "addressChanged"
|
||||||
@ -48,6 +49,9 @@ const (
|
|||||||
// SetupEvents specific to event type and data.
|
// SetupEvents specific to event type and data.
|
||||||
func SetupEvents(listener listener.Listener) {
|
func SetupEvents(listener listener.Listener) {
|
||||||
listener.SetLimit(LogoutEvent, LogoutEventTimeout)
|
listener.SetLimit(LogoutEvent, LogoutEventTimeout)
|
||||||
listener.SetBuffer(TLSCertIssue)
|
|
||||||
listener.SetBuffer(ErrorEvent)
|
listener.SetBuffer(ErrorEvent)
|
||||||
|
listener.SetBuffer(CredentialsErrorEvent)
|
||||||
|
listener.SetBuffer(InternetOffEvent)
|
||||||
|
listener.SetBuffer(UpgradeApplicationEvent)
|
||||||
|
listener.SetBuffer(TLSCertIssue)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -28,9 +28,9 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
mobileconfig "github.com/ProtonMail/go-apple-mobileconfig"
|
|
||||||
"github.com/ProtonMail/proton-bridge/internal/bridge"
|
"github.com/ProtonMail/proton-bridge/internal/bridge"
|
||||||
"github.com/ProtonMail/proton-bridge/internal/frontend/types"
|
"github.com/ProtonMail/proton-bridge/internal/frontend/types"
|
||||||
|
"github.com/ProtonMail/proton-bridge/pkg/mobileconfig"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() { //nolint[gochecknoinit]
|
func init() { //nolint[gochecknoinit]
|
||||||
@ -66,17 +66,17 @@ func (c *appleMail) Configure(imapPort, smtpPort int, imapSSL, smtpSSL bool, use
|
|||||||
EmailAddress: addresses,
|
EmailAddress: addresses,
|
||||||
DisplayName: displayName,
|
DisplayName: displayName,
|
||||||
Identifier: "protonmail " + displayName + timestamp,
|
Identifier: "protonmail " + displayName + timestamp,
|
||||||
Imap: &mobileconfig.Imap{
|
IMAP: &mobileconfig.IMAP{
|
||||||
Hostname: bridge.Host,
|
Hostname: bridge.Host,
|
||||||
Port: imapPort,
|
Port: imapPort,
|
||||||
Tls: imapSSL,
|
TLS: imapSSL,
|
||||||
Username: displayName,
|
Username: displayName,
|
||||||
Password: user.GetBridgePassword(),
|
Password: user.GetBridgePassword(),
|
||||||
},
|
},
|
||||||
Smtp: &mobileconfig.Smtp{
|
SMTP: &mobileconfig.SMTP{
|
||||||
Hostname: bridge.Host,
|
Hostname: bridge.Host,
|
||||||
Port: smtpPort,
|
Port: smtpPort,
|
||||||
Tls: smtpSSL,
|
TLS: smtpSSL,
|
||||||
Username: displayName,
|
Username: displayName,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -98,7 +98,7 @@ func (c *appleMail) Configure(imapPort, smtpPort int, imapSSL, smtpSSL bool, use
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := mc.WriteTo(f); err != nil {
|
if err := mc.WriteOut(f); err != nil {
|
||||||
_ = f.Close()
|
_ = f.Close()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@ -21,7 +21,8 @@ package cliie
|
|||||||
import (
|
import (
|
||||||
"github.com/ProtonMail/proton-bridge/internal/events"
|
"github.com/ProtonMail/proton-bridge/internal/events"
|
||||||
"github.com/ProtonMail/proton-bridge/internal/frontend/types"
|
"github.com/ProtonMail/proton-bridge/internal/frontend/types"
|
||||||
"github.com/ProtonMail/proton-bridge/pkg/config"
|
"github.com/ProtonMail/proton-bridge/internal/locations"
|
||||||
|
"github.com/ProtonMail/proton-bridge/internal/updater"
|
||||||
"github.com/ProtonMail/proton-bridge/pkg/listener"
|
"github.com/ProtonMail/proton-bridge/pkg/listener"
|
||||||
|
|
||||||
"github.com/abiosoft/ishell"
|
"github.com/abiosoft/ishell"
|
||||||
@ -35,31 +36,33 @@ var (
|
|||||||
type frontendCLI struct {
|
type frontendCLI struct {
|
||||||
*ishell.Shell
|
*ishell.Shell
|
||||||
|
|
||||||
config *config.Config
|
locations *locations.Locations
|
||||||
eventListener listener.Listener
|
eventListener listener.Listener
|
||||||
updates types.Updater
|
updater types.Updater
|
||||||
ie types.ImportExporter
|
ie types.ImportExporter
|
||||||
|
|
||||||
appRestart bool
|
restarter types.Restarter
|
||||||
}
|
}
|
||||||
|
|
||||||
// New returns a new CLI frontend configured with the given options.
|
// New returns a new CLI frontend configured with the given options.
|
||||||
func New( //nolint[funlen]
|
func New( //nolint[funlen]
|
||||||
panicHandler types.PanicHandler,
|
panicHandler types.PanicHandler,
|
||||||
config *config.Config,
|
|
||||||
|
locations *locations.Locations,
|
||||||
eventListener listener.Listener,
|
eventListener listener.Listener,
|
||||||
updates types.Updater,
|
updater types.Updater,
|
||||||
ie types.ImportExporter,
|
ie types.ImportExporter,
|
||||||
|
restarter types.Restarter,
|
||||||
) *frontendCLI { //nolint[golint]
|
) *frontendCLI { //nolint[golint]
|
||||||
fe := &frontendCLI{
|
fe := &frontendCLI{
|
||||||
Shell: ishell.New(),
|
Shell: ishell.New(),
|
||||||
|
|
||||||
config: config,
|
locations: locations,
|
||||||
eventListener: eventListener,
|
eventListener: eventListener,
|
||||||
updates: updates,
|
updater: updater,
|
||||||
ie: ie,
|
ie: ie,
|
||||||
|
|
||||||
appRestart: false,
|
restarter: restarter,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear commands.
|
// Clear commands.
|
||||||
@ -99,11 +102,6 @@ func New( //nolint[funlen]
|
|||||||
Aliases: []string{"man"},
|
Aliases: []string{"man"},
|
||||||
Func: fe.printManual,
|
Func: fe.printManual,
|
||||||
})
|
})
|
||||||
fe.AddCmd(&ishell.Cmd{Name: "release-notes",
|
|
||||||
Help: "print release notes. (aliases: notes, fixed-bugs, bugs, ver, version)",
|
|
||||||
Aliases: []string{"notes", "fixed-bugs", "bugs", "ver", "version"},
|
|
||||||
Func: fe.printLocalReleaseNotes,
|
|
||||||
})
|
|
||||||
fe.AddCmd(&ishell.Cmd{Name: "credits",
|
fe.AddCmd(&ishell.Cmd{Name: "credits",
|
||||||
Help: "print used resources.",
|
Help: "print used resources.",
|
||||||
Func: fe.printCredits,
|
Func: fe.printCredits,
|
||||||
@ -175,13 +173,12 @@ func New( //nolint[funlen]
|
|||||||
defer panicHandler.HandlePanic()
|
defer panicHandler.HandlePanic()
|
||||||
fe.watchEvents()
|
fe.watchEvents()
|
||||||
}()
|
}()
|
||||||
fe.eventListener.RetryEmit(events.TLSCertIssue)
|
|
||||||
fe.eventListener.RetryEmit(events.ErrorEvent)
|
|
||||||
return fe
|
return fe
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *frontendCLI) watchEvents() {
|
func (f *frontendCLI) watchEvents() {
|
||||||
errorCh := f.getEventChannel(events.ErrorEvent)
|
errorCh := f.getEventChannel(events.ErrorEvent)
|
||||||
|
credentialsErrorCh := f.getEventChannel(events.CredentialsErrorEvent)
|
||||||
internetOffCh := f.getEventChannel(events.InternetOffEvent)
|
internetOffCh := f.getEventChannel(events.InternetOffEvent)
|
||||||
internetOnCh := f.getEventChannel(events.InternetOnEvent)
|
internetOnCh := f.getEventChannel(events.InternetOnEvent)
|
||||||
addressChangedLogoutCh := f.getEventChannel(events.AddressChangedLogoutEvent)
|
addressChangedLogoutCh := f.getEventChannel(events.AddressChangedLogoutEvent)
|
||||||
@ -191,6 +188,8 @@ func (f *frontendCLI) watchEvents() {
|
|||||||
select {
|
select {
|
||||||
case errorDetails := <-errorCh:
|
case errorDetails := <-errorCh:
|
||||||
f.Println("Import-Export failed:", errorDetails)
|
f.Println("Import-Export failed:", errorDetails)
|
||||||
|
case <-credentialsErrorCh:
|
||||||
|
f.notifyCredentialsError()
|
||||||
case <-internetOffCh:
|
case <-internetOffCh:
|
||||||
f.notifyInternetOff()
|
f.notifyInternetOff()
|
||||||
case <-internetOnCh:
|
case <-internetOnCh:
|
||||||
@ -212,21 +211,12 @@ func (f *frontendCLI) watchEvents() {
|
|||||||
func (f *frontendCLI) getEventChannel(event string) <-chan string {
|
func (f *frontendCLI) getEventChannel(event string) <-chan string {
|
||||||
ch := make(chan string)
|
ch := make(chan string)
|
||||||
f.eventListener.Add(event, ch)
|
f.eventListener.Add(event, ch)
|
||||||
|
f.eventListener.RetryEmit(event)
|
||||||
return ch
|
return ch
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsAppRestarting returns whether the app is currently set to restart.
|
|
||||||
func (f *frontendCLI) IsAppRestarting() bool {
|
|
||||||
return f.appRestart
|
|
||||||
}
|
|
||||||
|
|
||||||
// Loop starts the frontend loop with an interactive shell.
|
// Loop starts the frontend loop with an interactive shell.
|
||||||
func (f *frontendCLI) Loop(credentialsError error) error {
|
func (f *frontendCLI) Loop() error {
|
||||||
if credentialsError != nil {
|
|
||||||
f.notifyCredentialsError()
|
|
||||||
return credentialsError
|
|
||||||
}
|
|
||||||
|
|
||||||
f.Print(`
|
f.Print(`
|
||||||
Welcome to ProtonMail Import-Export app interactive shell
|
Welcome to ProtonMail Import-Export app interactive shell
|
||||||
|
|
||||||
@ -235,3 +225,12 @@ WARNING: The CLI is an experimental feature and does not yet cover all functiona
|
|||||||
f.Run()
|
f.Run()
|
||||||
return nil
|
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) {}
|
||||||
|
|||||||
@ -24,7 +24,7 @@ import (
|
|||||||
func (f *frontendCLI) restart(c *ishell.Context) {
|
func (f *frontendCLI) restart(c *ishell.Context) {
|
||||||
if f.yesNoQuestion("Are you sure you want to restart the Import-Export app") {
|
if f.yesNoQuestion("Are you sure you want to restart the Import-Export app") {
|
||||||
f.Println("Restarting the Import-Export app...")
|
f.Println("Restarting the Import-Export app...")
|
||||||
f.appRestart = true
|
f.restarter.SetToRestart()
|
||||||
f.Stop()
|
f.Stop()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -38,7 +38,11 @@ func (f *frontendCLI) checkInternetConnection(c *ishell.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (f *frontendCLI) printLogDir(c *ishell.Context) {
|
func (f *frontendCLI) printLogDir(c *ishell.Context) {
|
||||||
f.Println("Log files are stored in\n\n ", f.config.GetLogDir())
|
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) {
|
func (f *frontendCLI) printManual(c *ishell.Context) {
|
||||||
|
|||||||
@ -21,41 +21,11 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/ProtonMail/proton-bridge/internal/importexport"
|
"github.com/ProtonMail/proton-bridge/internal/importexport"
|
||||||
"github.com/ProtonMail/proton-bridge/internal/updates"
|
|
||||||
"github.com/abiosoft/ishell"
|
"github.com/abiosoft/ishell"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (f *frontendCLI) checkUpdates(c *ishell.Context) {
|
func (f *frontendCLI) checkUpdates(c *ishell.Context) {
|
||||||
isUpToDate, latestVersionInfo, err := f.updates.CheckIsUpToDate()
|
f.Println("Your version is up to date.")
|
||||||
if err != nil {
|
|
||||||
f.printAndLogError("Cannot retrieve version info: ", err)
|
|
||||||
f.checkInternetConnection(c)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if isUpToDate {
|
|
||||||
f.Println("Your version is up to date.")
|
|
||||||
} else {
|
|
||||||
f.notifyNeedUpgrade()
|
|
||||||
f.Println("")
|
|
||||||
f.printReleaseNotes(latestVersionInfo)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *frontendCLI) printLocalReleaseNotes(c *ishell.Context) {
|
|
||||||
localVersion := f.updates.GetLocalVersion()
|
|
||||||
f.printReleaseNotes(localVersion)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *frontendCLI) printReleaseNotes(versionInfo updates.VersionInfo) {
|
|
||||||
f.Println(bold("ProtonMail Import-Export app "+versionInfo.Version), "\n")
|
|
||||||
if versionInfo.ReleaseNotes != "" {
|
|
||||||
f.Println(bold("Release Notes"))
|
|
||||||
f.Println(versionInfo.ReleaseNotes)
|
|
||||||
}
|
|
||||||
if versionInfo.ReleaseFixedBugs != "" {
|
|
||||||
f.Println(bold("Fixed bugs"))
|
|
||||||
f.Println(versionInfo.ReleaseFixedBugs)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *frontendCLI) printCredits(c *ishell.Context) {
|
func (f *frontendCLI) printCredits(c *ishell.Context) {
|
||||||
|
|||||||
@ -93,10 +93,15 @@ func (f *frontendCLI) notifyLogout(address string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (f *frontendCLI) notifyNeedUpgrade() {
|
func (f *frontendCLI) notifyNeedUpgrade() {
|
||||||
f.Println("Please download and install the newest version of application from", f.updates.GetDownloadLink())
|
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() {
|
func (f *frontendCLI) notifyCredentialsError() { // nolint[unused]
|
||||||
// Print in 80-column width.
|
// Print in 80-column width.
|
||||||
f.Println("ProtonMail Import-Export app is not able to detect a supported password manager")
|
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("(pass, gnome-keyring). Please install and set up a supported password manager")
|
||||||
|
|||||||
@ -21,8 +21,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/ProtonMail/proton-bridge/internal/bridge"
|
"github.com/ProtonMail/proton-bridge/internal/bridge"
|
||||||
|
"github.com/ProtonMail/proton-bridge/internal/config/settings"
|
||||||
"github.com/ProtonMail/proton-bridge/internal/frontend/types"
|
"github.com/ProtonMail/proton-bridge/internal/frontend/types"
|
||||||
"github.com/ProtonMail/proton-bridge/internal/preferences"
|
|
||||||
"github.com/abiosoft/ishell"
|
"github.com/abiosoft/ishell"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -65,13 +65,13 @@ func (f *frontendCLI) showAccountInfo(c *ishell.Context) {
|
|||||||
|
|
||||||
func (f *frontendCLI) showAccountAddressInfo(user types.User, address string) {
|
func (f *frontendCLI) showAccountAddressInfo(user types.User, address string) {
|
||||||
smtpSecurity := "STARTTLS"
|
smtpSecurity := "STARTTLS"
|
||||||
if f.preferences.GetBool(preferences.SMTPSSLKey) {
|
if f.settings.GetBool(settings.SMTPSSLKey) {
|
||||||
smtpSecurity = "SSL"
|
smtpSecurity = "SSL"
|
||||||
}
|
}
|
||||||
f.Println(bold("Configuration for " + address))
|
f.Println(bold("Configuration for " + address))
|
||||||
f.Printf("IMAP Settings\nAddress: %s\nIMAP port: %d\nUsername: %s\nPassword: %s\nSecurity: %s\n",
|
f.Printf("IMAP Settings\nAddress: %s\nIMAP port: %d\nUsername: %s\nPassword: %s\nSecurity: %s\n",
|
||||||
bridge.Host,
|
bridge.Host,
|
||||||
f.preferences.GetInt(preferences.IMAPPortKey),
|
f.settings.GetInt(settings.IMAPPortKey),
|
||||||
address,
|
address,
|
||||||
user.GetBridgePassword(),
|
user.GetBridgePassword(),
|
||||||
"STARTTLS",
|
"STARTTLS",
|
||||||
@ -79,7 +79,7 @@ func (f *frontendCLI) showAccountAddressInfo(user types.User, address string) {
|
|||||||
f.Println("")
|
f.Println("")
|
||||||
f.Printf("SMTP Settings\nAddress: %s\nSMTP port: %d\nUsername: %s\nPassword: %s\nSecurity: %s\n",
|
f.Printf("SMTP Settings\nAddress: %s\nSMTP port: %d\nUsername: %s\nPassword: %s\nSecurity: %s\n",
|
||||||
bridge.Host,
|
bridge.Host,
|
||||||
f.preferences.GetInt(preferences.SMTPPortKey),
|
f.settings.GetInt(settings.SMTPPortKey),
|
||||||
address,
|
address,
|
||||||
user.GetBridgePassword(),
|
user.GetBridgePassword(),
|
||||||
smtpSecurity,
|
smtpSecurity,
|
||||||
|
|||||||
@ -19,9 +19,11 @@
|
|||||||
package cli
|
package cli
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/ProtonMail/proton-bridge/internal/config/settings"
|
||||||
"github.com/ProtonMail/proton-bridge/internal/events"
|
"github.com/ProtonMail/proton-bridge/internal/events"
|
||||||
"github.com/ProtonMail/proton-bridge/internal/frontend/types"
|
"github.com/ProtonMail/proton-bridge/internal/frontend/types"
|
||||||
"github.com/ProtonMail/proton-bridge/pkg/config"
|
"github.com/ProtonMail/proton-bridge/internal/locations"
|
||||||
|
"github.com/ProtonMail/proton-bridge/internal/updater"
|
||||||
"github.com/ProtonMail/proton-bridge/pkg/listener"
|
"github.com/ProtonMail/proton-bridge/pkg/listener"
|
||||||
|
|
||||||
"github.com/abiosoft/ishell"
|
"github.com/abiosoft/ishell"
|
||||||
@ -35,34 +37,36 @@ var (
|
|||||||
type frontendCLI struct {
|
type frontendCLI struct {
|
||||||
*ishell.Shell
|
*ishell.Shell
|
||||||
|
|
||||||
config *config.Config
|
locations *locations.Locations
|
||||||
preferences *config.Preferences
|
settings *settings.Settings
|
||||||
eventListener listener.Listener
|
eventListener listener.Listener
|
||||||
updates types.Updater
|
updater types.Updater
|
||||||
bridge types.Bridger
|
bridge types.Bridger
|
||||||
|
|
||||||
appRestart bool
|
restarter types.Restarter
|
||||||
}
|
}
|
||||||
|
|
||||||
// New returns a new CLI frontend configured with the given options.
|
// New returns a new CLI frontend configured with the given options.
|
||||||
func New( //nolint[funlen]
|
func New( //nolint[funlen]
|
||||||
panicHandler types.PanicHandler,
|
panicHandler types.PanicHandler,
|
||||||
config *config.Config,
|
|
||||||
preferences *config.Preferences,
|
locations *locations.Locations,
|
||||||
|
settings *settings.Settings,
|
||||||
eventListener listener.Listener,
|
eventListener listener.Listener,
|
||||||
updates types.Updater,
|
updater types.Updater,
|
||||||
bridge types.Bridger,
|
bridge types.Bridger,
|
||||||
|
restarter types.Restarter,
|
||||||
) *frontendCLI { //nolint[golint]
|
) *frontendCLI { //nolint[golint]
|
||||||
fe := &frontendCLI{
|
fe := &frontendCLI{
|
||||||
Shell: ishell.New(),
|
Shell: ishell.New(),
|
||||||
|
|
||||||
config: config,
|
locations: locations,
|
||||||
preferences: preferences,
|
settings: settings,
|
||||||
eventListener: eventListener,
|
eventListener: eventListener,
|
||||||
updates: updates,
|
updater: updater,
|
||||||
bridge: bridge,
|
bridge: bridge,
|
||||||
|
|
||||||
appRestart: false,
|
restarter: restarter,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear commands.
|
// Clear commands.
|
||||||
@ -134,11 +138,7 @@ func New( //nolint[funlen]
|
|||||||
Aliases: []string{"man"},
|
Aliases: []string{"man"},
|
||||||
Func: fe.printManual,
|
Func: fe.printManual,
|
||||||
})
|
})
|
||||||
fe.AddCmd(&ishell.Cmd{Name: "release-notes",
|
|
||||||
Help: "print release notes. (aliases: notes, fixed-bugs, bugs, ver, version)",
|
|
||||||
Aliases: []string{"notes", "fixed-bugs", "bugs", "ver", "version"},
|
|
||||||
Func: fe.printLocalReleaseNotes,
|
|
||||||
})
|
|
||||||
fe.AddCmd(&ishell.Cmd{Name: "credits",
|
fe.AddCmd(&ishell.Cmd{Name: "credits",
|
||||||
Help: "print used resources.",
|
Help: "print used resources.",
|
||||||
Func: fe.printCredits,
|
Func: fe.printCredits,
|
||||||
@ -185,13 +185,12 @@ func New( //nolint[funlen]
|
|||||||
defer panicHandler.HandlePanic()
|
defer panicHandler.HandlePanic()
|
||||||
fe.watchEvents()
|
fe.watchEvents()
|
||||||
}()
|
}()
|
||||||
fe.eventListener.RetryEmit(events.TLSCertIssue)
|
|
||||||
fe.eventListener.RetryEmit(events.ErrorEvent)
|
|
||||||
return fe
|
return fe
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *frontendCLI) watchEvents() {
|
func (f *frontendCLI) watchEvents() {
|
||||||
errorCh := f.getEventChannel(events.ErrorEvent)
|
errorCh := f.getEventChannel(events.ErrorEvent)
|
||||||
|
credentialsErrorCh := f.getEventChannel(events.CredentialsErrorEvent)
|
||||||
internetOffCh := f.getEventChannel(events.InternetOffEvent)
|
internetOffCh := f.getEventChannel(events.InternetOffEvent)
|
||||||
internetOnCh := f.getEventChannel(events.InternetOnEvent)
|
internetOnCh := f.getEventChannel(events.InternetOnEvent)
|
||||||
addressChangedCh := f.getEventChannel(events.AddressChangedEvent)
|
addressChangedCh := f.getEventChannel(events.AddressChangedEvent)
|
||||||
@ -202,6 +201,8 @@ func (f *frontendCLI) watchEvents() {
|
|||||||
select {
|
select {
|
||||||
case errorDetails := <-errorCh:
|
case errorDetails := <-errorCh:
|
||||||
f.Println("Bridge failed:", errorDetails)
|
f.Println("Bridge failed:", errorDetails)
|
||||||
|
case <-credentialsErrorCh:
|
||||||
|
f.notifyCredentialsError()
|
||||||
case <-internetOffCh:
|
case <-internetOffCh:
|
||||||
f.notifyInternetOff()
|
f.notifyInternetOff()
|
||||||
case <-internetOnCh:
|
case <-internetOnCh:
|
||||||
@ -225,21 +226,12 @@ func (f *frontendCLI) watchEvents() {
|
|||||||
func (f *frontendCLI) getEventChannel(event string) <-chan string {
|
func (f *frontendCLI) getEventChannel(event string) <-chan string {
|
||||||
ch := make(chan string)
|
ch := make(chan string)
|
||||||
f.eventListener.Add(event, ch)
|
f.eventListener.Add(event, ch)
|
||||||
|
f.eventListener.RetryEmit(event)
|
||||||
return ch
|
return ch
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsAppRestarting returns whether the app is currently set to restart.
|
|
||||||
func (f *frontendCLI) IsAppRestarting() bool {
|
|
||||||
return f.appRestart
|
|
||||||
}
|
|
||||||
|
|
||||||
// Loop starts the frontend loop with an interactive shell.
|
// Loop starts the frontend loop with an interactive shell.
|
||||||
func (f *frontendCLI) Loop(credentialsError error) error {
|
func (f *frontendCLI) Loop() error {
|
||||||
if credentialsError != nil {
|
|
||||||
f.notifyCredentialsError()
|
|
||||||
return credentialsError
|
|
||||||
}
|
|
||||||
|
|
||||||
f.Print(`
|
f.Print(`
|
||||||
Welcome to ProtonMail Bridge interactive shell
|
Welcome to ProtonMail Bridge interactive shell
|
||||||
___....___
|
___....___
|
||||||
@ -260,3 +252,12 @@ func (f *frontendCLI) Loop(credentialsError error) error {
|
|||||||
f.Run()
|
f.Run()
|
||||||
return nil
|
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) {}
|
||||||
|
|||||||
@ -22,7 +22,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/ProtonMail/proton-bridge/internal/preferences"
|
"github.com/ProtonMail/proton-bridge/internal/config/settings"
|
||||||
"github.com/ProtonMail/proton-bridge/pkg/ports"
|
"github.com/ProtonMail/proton-bridge/pkg/ports"
|
||||||
"github.com/abiosoft/ishell"
|
"github.com/abiosoft/ishell"
|
||||||
)
|
)
|
||||||
@ -34,7 +34,7 @@ var (
|
|||||||
func (f *frontendCLI) restart(c *ishell.Context) {
|
func (f *frontendCLI) restart(c *ishell.Context) {
|
||||||
if f.yesNoQuestion("Are you sure you want to restart the Bridge") {
|
if f.yesNoQuestion("Are you sure you want to restart the Bridge") {
|
||||||
f.Println("Restarting Bridge...")
|
f.Println("Restarting Bridge...")
|
||||||
f.appRestart = true
|
f.restarter.SetToRestart()
|
||||||
f.Stop()
|
f.Stop()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -48,7 +48,11 @@ func (f *frontendCLI) checkInternetConnection(c *ishell.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (f *frontendCLI) printLogDir(c *ishell.Context) {
|
func (f *frontendCLI) printLogDir(c *ishell.Context) {
|
||||||
f.Println("Log files are stored in\n\n ", f.config.GetLogDir())
|
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) {
|
func (f *frontendCLI) printManual(c *ishell.Context) {
|
||||||
@ -69,7 +73,7 @@ func (f *frontendCLI) deleteCache(c *ishell.Context) {
|
|||||||
f.Println("Cached cleared, restarting bridge")
|
f.Println("Cached cleared, restarting bridge")
|
||||||
// Clearing data removes everything (db, preferences, ...)
|
// Clearing data removes everything (db, preferences, ...)
|
||||||
// so everything has to be stopped and started again.
|
// so everything has to be stopped and started again.
|
||||||
f.appRestart = true
|
f.restarter.SetToRestart()
|
||||||
f.Stop()
|
f.Stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,7 +81,7 @@ func (f *frontendCLI) changeSMTPSecurity(c *ishell.Context) {
|
|||||||
f.ShowPrompt(false)
|
f.ShowPrompt(false)
|
||||||
defer f.ShowPrompt(true)
|
defer f.ShowPrompt(true)
|
||||||
|
|
||||||
isSSL := f.preferences.GetBool(preferences.SMTPSSLKey)
|
isSSL := f.settings.GetBool(settings.SMTPSSLKey)
|
||||||
newSecurity := "SSL"
|
newSecurity := "SSL"
|
||||||
if isSSL {
|
if isSSL {
|
||||||
newSecurity = "STARTTLS"
|
newSecurity = "STARTTLS"
|
||||||
@ -86,9 +90,9 @@ func (f *frontendCLI) changeSMTPSecurity(c *ishell.Context) {
|
|||||||
msg := fmt.Sprintf("Are you sure you want to change SMTP setting to %q and restart the Bridge", newSecurity)
|
msg := fmt.Sprintf("Are you sure you want to change SMTP setting to %q and restart the Bridge", newSecurity)
|
||||||
|
|
||||||
if f.yesNoQuestion(msg) {
|
if f.yesNoQuestion(msg) {
|
||||||
f.preferences.SetBool(preferences.SMTPSSLKey, !isSSL)
|
f.settings.SetBool(settings.SMTPSSLKey, !isSSL)
|
||||||
f.Println("Restarting Bridge...")
|
f.Println("Restarting Bridge...")
|
||||||
f.appRestart = true
|
f.restarter.SetToRestart()
|
||||||
f.Stop()
|
f.Stop()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -97,14 +101,14 @@ func (f *frontendCLI) changePort(c *ishell.Context) {
|
|||||||
f.ShowPrompt(false)
|
f.ShowPrompt(false)
|
||||||
defer f.ShowPrompt(true)
|
defer f.ShowPrompt(true)
|
||||||
|
|
||||||
currentPort = f.preferences.Get(preferences.IMAPPortKey)
|
currentPort = f.settings.Get(settings.IMAPPortKey)
|
||||||
newIMAPPort := f.readStringInAttempts("Set IMAP port (current "+currentPort+")", c.ReadLine, f.isPortFree)
|
newIMAPPort := f.readStringInAttempts("Set IMAP port (current "+currentPort+")", c.ReadLine, f.isPortFree)
|
||||||
if newIMAPPort == "" {
|
if newIMAPPort == "" {
|
||||||
newIMAPPort = currentPort
|
newIMAPPort = currentPort
|
||||||
}
|
}
|
||||||
imapPortChanged := newIMAPPort != currentPort
|
imapPortChanged := newIMAPPort != currentPort
|
||||||
|
|
||||||
currentPort = f.preferences.Get(preferences.SMTPPortKey)
|
currentPort = f.settings.Get(settings.SMTPPortKey)
|
||||||
newSMTPPort := f.readStringInAttempts("Set SMTP port (current "+currentPort+")", c.ReadLine, f.isPortFree)
|
newSMTPPort := f.readStringInAttempts("Set SMTP port (current "+currentPort+")", c.ReadLine, f.isPortFree)
|
||||||
if newSMTPPort == "" {
|
if newSMTPPort == "" {
|
||||||
newSMTPPort = currentPort
|
newSMTPPort = currentPort
|
||||||
@ -118,10 +122,10 @@ func (f *frontendCLI) changePort(c *ishell.Context) {
|
|||||||
|
|
||||||
if imapPortChanged || smtpPortChanged {
|
if imapPortChanged || smtpPortChanged {
|
||||||
f.Println("Saving values IMAP:", newIMAPPort, "SMTP:", newSMTPPort)
|
f.Println("Saving values IMAP:", newIMAPPort, "SMTP:", newSMTPPort)
|
||||||
f.preferences.Set(preferences.IMAPPortKey, newIMAPPort)
|
f.settings.Set(settings.IMAPPortKey, newIMAPPort)
|
||||||
f.preferences.Set(preferences.SMTPPortKey, newSMTPPort)
|
f.settings.Set(settings.SMTPPortKey, newSMTPPort)
|
||||||
f.Println("Restarting Bridge...")
|
f.Println("Restarting Bridge...")
|
||||||
f.appRestart = true
|
f.restarter.SetToRestart()
|
||||||
f.Stop()
|
f.Stop()
|
||||||
} else {
|
} else {
|
||||||
f.Println("Nothing changed")
|
f.Println("Nothing changed")
|
||||||
@ -129,16 +133,16 @@ func (f *frontendCLI) changePort(c *ishell.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (f *frontendCLI) toggleAllowProxy(c *ishell.Context) {
|
func (f *frontendCLI) toggleAllowProxy(c *ishell.Context) {
|
||||||
if f.preferences.GetBool(preferences.AllowProxyKey) {
|
if f.settings.GetBool(settings.AllowProxyKey) {
|
||||||
f.Println("Bridge is currently set to use alternative routing to connect to Proton if it is being blocked.")
|
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") {
|
if f.yesNoQuestion("Are you sure you want to stop bridge from doing this") {
|
||||||
f.preferences.SetBool(preferences.AllowProxyKey, false)
|
f.settings.SetBool(settings.AllowProxyKey, false)
|
||||||
f.bridge.DisallowProxy()
|
f.bridge.DisallowProxy()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
f.Println("Bridge is currently set to NOT use alternative routing to connect to Proton if it is being blocked.")
|
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") {
|
if f.yesNoQuestion("Are you sure you want to allow bridge to do this") {
|
||||||
f.preferences.SetBool(preferences.AllowProxyKey, true)
|
f.settings.SetBool(settings.AllowProxyKey, true)
|
||||||
f.bridge.AllowProxy()
|
f.bridge.AllowProxy()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -21,41 +21,11 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/ProtonMail/proton-bridge/internal/bridge"
|
"github.com/ProtonMail/proton-bridge/internal/bridge"
|
||||||
"github.com/ProtonMail/proton-bridge/internal/updates"
|
|
||||||
"github.com/abiosoft/ishell"
|
"github.com/abiosoft/ishell"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (f *frontendCLI) checkUpdates(c *ishell.Context) {
|
func (f *frontendCLI) checkUpdates(c *ishell.Context) {
|
||||||
isUpToDate, latestVersionInfo, err := f.updates.CheckIsUpToDate()
|
f.Println("Your version is up to date.")
|
||||||
if err != nil {
|
|
||||||
f.printAndLogError("Cannot retrieve version info: ", err)
|
|
||||||
f.checkInternetConnection(c)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if isUpToDate {
|
|
||||||
f.Println("Your version is up to date.")
|
|
||||||
} else {
|
|
||||||
f.notifyNeedUpgrade()
|
|
||||||
f.Println("")
|
|
||||||
f.printReleaseNotes(latestVersionInfo)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *frontendCLI) printLocalReleaseNotes(c *ishell.Context) {
|
|
||||||
localVersion := f.updates.GetLocalVersion()
|
|
||||||
f.printReleaseNotes(localVersion)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *frontendCLI) printReleaseNotes(versionInfo updates.VersionInfo) {
|
|
||||||
f.Println(bold("ProtonMail Bridge "+versionInfo.Version), "\n")
|
|
||||||
if versionInfo.ReleaseNotes != "" {
|
|
||||||
f.Println(bold("Release Notes"))
|
|
||||||
f.Println(versionInfo.ReleaseNotes)
|
|
||||||
}
|
|
||||||
if versionInfo.ReleaseFixedBugs != "" {
|
|
||||||
f.Println(bold("Fixed bugs"))
|
|
||||||
f.Println(versionInfo.ReleaseFixedBugs)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *frontendCLI) printCredits(c *ishell.Context) {
|
func (f *frontendCLI) printCredits(c *ishell.Context) {
|
||||||
|
|||||||
@ -93,10 +93,15 @@ func (f *frontendCLI) notifyLogout(address string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (f *frontendCLI) notifyNeedUpgrade() {
|
func (f *frontendCLI) notifyNeedUpgrade() {
|
||||||
f.Println("Please download and install the newest version of application from", f.updates.GetDownloadLink())
|
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() {
|
func (f *frontendCLI) notifyCredentialsError() { // nolint[unused]
|
||||||
// Print in 80-column width.
|
// Print in 80-column width.
|
||||||
f.Println("ProtonMail Bridge is not able to detect a supported password manager")
|
f.Println("ProtonMail Bridge 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("(pass, gnome-keyring). Please install and set up a supported password manager")
|
||||||
|
|||||||
@ -19,15 +19,17 @@
|
|||||||
package frontend
|
package frontend
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/0xAX/notificator"
|
"github.com/ProtonMail/go-autostart"
|
||||||
"github.com/ProtonMail/proton-bridge/internal/bridge"
|
"github.com/ProtonMail/proton-bridge/internal/bridge"
|
||||||
|
"github.com/ProtonMail/proton-bridge/internal/config/settings"
|
||||||
"github.com/ProtonMail/proton-bridge/internal/frontend/cli"
|
"github.com/ProtonMail/proton-bridge/internal/frontend/cli"
|
||||||
cliie "github.com/ProtonMail/proton-bridge/internal/frontend/cli-ie"
|
cliie "github.com/ProtonMail/proton-bridge/internal/frontend/cli-ie"
|
||||||
"github.com/ProtonMail/proton-bridge/internal/frontend/qt"
|
"github.com/ProtonMail/proton-bridge/internal/frontend/qt"
|
||||||
qtie "github.com/ProtonMail/proton-bridge/internal/frontend/qt-ie"
|
qtie "github.com/ProtonMail/proton-bridge/internal/frontend/qt-ie"
|
||||||
"github.com/ProtonMail/proton-bridge/internal/frontend/types"
|
"github.com/ProtonMail/proton-bridge/internal/frontend/types"
|
||||||
"github.com/ProtonMail/proton-bridge/internal/importexport"
|
"github.com/ProtonMail/proton-bridge/internal/importexport"
|
||||||
"github.com/ProtonMail/proton-bridge/pkg/config"
|
"github.com/ProtonMail/proton-bridge/internal/locations"
|
||||||
|
"github.com/ProtonMail/proton-bridge/internal/updater"
|
||||||
"github.com/ProtonMail/proton-bridge/pkg/listener"
|
"github.com/ProtonMail/proton-bridge/pkg/listener"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
@ -38,55 +40,93 @@ var (
|
|||||||
|
|
||||||
// Frontend is an interface to be implemented by each frontend type (cli, gui, html).
|
// Frontend is an interface to be implemented by each frontend type (cli, gui, html).
|
||||||
type Frontend interface {
|
type Frontend interface {
|
||||||
Loop(credentialsError error) error
|
Loop() error
|
||||||
IsAppRestarting() bool
|
NotifyManualUpdate(update updater.VersionInfo, canInstall bool)
|
||||||
}
|
SetVersion(update updater.VersionInfo)
|
||||||
|
NotifySilentUpdateInstalled()
|
||||||
// HandlePanic handles panics which occur for users with GUI.
|
NotifySilentUpdateError(error)
|
||||||
func HandlePanic(appName string) {
|
WaitUntilFrontendIsReady()
|
||||||
notify := notificator.New(notificator.Options{
|
|
||||||
DefaultIcon: "../frontend/ui/icon/icon.png",
|
|
||||||
AppName: appName,
|
|
||||||
})
|
|
||||||
_ = notify.Push("Fatal Error", "The "+appName+" has encountered a fatal error. ", "/frontend/icon/icon.png", notificator.UR_CRITICAL)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// New returns initialized frontend based on `frontendType`, which can be `cli` or `qt`.
|
// New returns initialized frontend based on `frontendType`, which can be `cli` or `qt`.
|
||||||
func New(
|
func New(
|
||||||
version,
|
version,
|
||||||
buildVersion,
|
buildVersion,
|
||||||
|
programName,
|
||||||
frontendType string,
|
frontendType string,
|
||||||
showWindowOnStart bool,
|
showWindowOnStart bool,
|
||||||
panicHandler types.PanicHandler,
|
panicHandler types.PanicHandler,
|
||||||
config *config.Config,
|
locations *locations.Locations,
|
||||||
preferences *config.Preferences,
|
settings *settings.Settings,
|
||||||
eventListener listener.Listener,
|
eventListener listener.Listener,
|
||||||
updates types.Updater,
|
updater types.Updater,
|
||||||
bridge *bridge.Bridge,
|
bridge *bridge.Bridge,
|
||||||
noEncConfirmator types.NoEncConfirmator,
|
noEncConfirmator types.NoEncConfirmator,
|
||||||
|
autostart *autostart.App,
|
||||||
|
restarter types.Restarter,
|
||||||
) Frontend {
|
) Frontend {
|
||||||
bridgeWrap := types.NewBridgeWrap(bridge)
|
bridgeWrap := types.NewBridgeWrap(bridge)
|
||||||
return new(version, buildVersion, frontendType, showWindowOnStart, panicHandler, config, preferences, eventListener, updates, bridgeWrap, noEncConfirmator)
|
return newBridgeFrontend(
|
||||||
|
version,
|
||||||
|
buildVersion,
|
||||||
|
programName,
|
||||||
|
frontendType,
|
||||||
|
showWindowOnStart,
|
||||||
|
panicHandler,
|
||||||
|
locations,
|
||||||
|
settings,
|
||||||
|
eventListener,
|
||||||
|
updater,
|
||||||
|
bridgeWrap,
|
||||||
|
noEncConfirmator,
|
||||||
|
autostart,
|
||||||
|
restarter,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func new(
|
func newBridgeFrontend(
|
||||||
version,
|
version,
|
||||||
buildVersion,
|
buildVersion,
|
||||||
|
programName,
|
||||||
frontendType string,
|
frontendType string,
|
||||||
showWindowOnStart bool,
|
showWindowOnStart bool,
|
||||||
panicHandler types.PanicHandler,
|
panicHandler types.PanicHandler,
|
||||||
config *config.Config,
|
locations *locations.Locations,
|
||||||
preferences *config.Preferences,
|
settings *settings.Settings,
|
||||||
eventListener listener.Listener,
|
eventListener listener.Listener,
|
||||||
updates types.Updater,
|
updater types.Updater,
|
||||||
bridge types.Bridger,
|
bridge types.Bridger,
|
||||||
noEncConfirmator types.NoEncConfirmator,
|
noEncConfirmator types.NoEncConfirmator,
|
||||||
|
autostart *autostart.App,
|
||||||
|
restarter types.Restarter,
|
||||||
) Frontend {
|
) Frontend {
|
||||||
switch frontendType {
|
switch frontendType {
|
||||||
case "cli":
|
case "cli":
|
||||||
return cli.New(panicHandler, config, preferences, eventListener, updates, bridge)
|
return cli.New(
|
||||||
|
panicHandler,
|
||||||
|
locations,
|
||||||
|
settings,
|
||||||
|
eventListener,
|
||||||
|
updater,
|
||||||
|
bridge,
|
||||||
|
restarter,
|
||||||
|
)
|
||||||
default:
|
default:
|
||||||
return qt.New(version, buildVersion, showWindowOnStart, panicHandler, config, preferences, eventListener, updates, bridge, noEncConfirmator)
|
return qt.New(
|
||||||
|
version,
|
||||||
|
buildVersion,
|
||||||
|
programName,
|
||||||
|
showWindowOnStart,
|
||||||
|
panicHandler,
|
||||||
|
locations,
|
||||||
|
settings,
|
||||||
|
eventListener,
|
||||||
|
updater,
|
||||||
|
bridge,
|
||||||
|
noEncConfirmator,
|
||||||
|
autostart,
|
||||||
|
restarter,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -94,31 +134,67 @@ func new(
|
|||||||
func NewImportExport(
|
func NewImportExport(
|
||||||
version,
|
version,
|
||||||
buildVersion,
|
buildVersion,
|
||||||
|
programName,
|
||||||
frontendType string,
|
frontendType string,
|
||||||
panicHandler types.PanicHandler,
|
panicHandler types.PanicHandler,
|
||||||
config *config.Config,
|
locations *locations.Locations,
|
||||||
|
settings *settings.Settings,
|
||||||
eventListener listener.Listener,
|
eventListener listener.Listener,
|
||||||
updates types.Updater,
|
updater types.Updater,
|
||||||
ie *importexport.ImportExport,
|
ie *importexport.ImportExport,
|
||||||
|
restarter types.Restarter,
|
||||||
) Frontend {
|
) Frontend {
|
||||||
ieWrap := types.NewImportExportWrap(ie)
|
ieWrap := types.NewImportExportWrap(ie)
|
||||||
return newImportExport(version, buildVersion, frontendType, panicHandler, config, eventListener, updates, ieWrap)
|
return newIEFrontend(
|
||||||
|
version,
|
||||||
|
buildVersion,
|
||||||
|
programName,
|
||||||
|
frontendType,
|
||||||
|
panicHandler,
|
||||||
|
locations,
|
||||||
|
settings,
|
||||||
|
eventListener,
|
||||||
|
updater,
|
||||||
|
ieWrap,
|
||||||
|
restarter,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func newImportExport(
|
func newIEFrontend(
|
||||||
version,
|
version,
|
||||||
buildVersion,
|
buildVersion,
|
||||||
|
programName,
|
||||||
frontendType string,
|
frontendType string,
|
||||||
panicHandler types.PanicHandler,
|
panicHandler types.PanicHandler,
|
||||||
config *config.Config,
|
locations *locations.Locations,
|
||||||
|
settings *settings.Settings,
|
||||||
eventListener listener.Listener,
|
eventListener listener.Listener,
|
||||||
updates types.Updater,
|
updater types.Updater,
|
||||||
ie types.ImportExporter,
|
ie types.ImportExporter,
|
||||||
|
restarter types.Restarter,
|
||||||
) Frontend {
|
) Frontend {
|
||||||
switch frontendType {
|
switch frontendType {
|
||||||
case "cli":
|
case "cli":
|
||||||
return cliie.New(panicHandler, config, eventListener, updates, ie)
|
return cliie.New(
|
||||||
|
panicHandler,
|
||||||
|
locations,
|
||||||
|
eventListener,
|
||||||
|
updater,
|
||||||
|
ie,
|
||||||
|
restarter,
|
||||||
|
)
|
||||||
default:
|
default:
|
||||||
return qtie.New(version, buildVersion, panicHandler, config, eventListener, updates, ie)
|
return qtie.New(
|
||||||
|
version,
|
||||||
|
buildVersion,
|
||||||
|
programName,
|
||||||
|
panicHandler,
|
||||||
|
locations,
|
||||||
|
settings,
|
||||||
|
eventListener,
|
||||||
|
updater,
|
||||||
|
ie,
|
||||||
|
restarter,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -226,7 +226,7 @@ Dialog {
|
|||||||
target: timer
|
target: timer
|
||||||
onTriggered: {
|
onTriggered: {
|
||||||
go.setPortsAndSecurity(imapPort.text, smtpPort.text, securitySMTPSTARTTLS.checked)
|
go.setPortsAndSecurity(imapPort.text, smtpPort.text, securitySMTPSTARTTLS.checked)
|
||||||
go.isRestarting = true
|
go.setToRestart()
|
||||||
Qt.quit()
|
Qt.quit()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -137,6 +137,7 @@ Dialog {
|
|||||||
spacing: Style.dialog.spacing
|
spacing: Style.dialog.spacing
|
||||||
ButtonRounded {
|
ButtonRounded {
|
||||||
id:buttonNo
|
id:buttonNo
|
||||||
|
visible: root.state != "toggleEarlyAccess"
|
||||||
color_main: Style.dialog.text
|
color_main: Style.dialog.text
|
||||||
fa_icon: Style.fa.times
|
fa_icon: Style.fa.times
|
||||||
text: qsTr("No")
|
text: qsTr("No")
|
||||||
@ -148,7 +149,7 @@ Dialog {
|
|||||||
color_minor: Style.main.textBlue
|
color_minor: Style.main.textBlue
|
||||||
isOpaque: true
|
isOpaque: true
|
||||||
fa_icon: Style.fa.check
|
fa_icon: Style.fa.check
|
||||||
text: qsTr("Yes")
|
text: root.state == "toggleEarlyAccess" ? qsTr("Ok") : qsTr("Yes")
|
||||||
onClicked : {
|
onClicked : {
|
||||||
currentIndex=1
|
currentIndex=1
|
||||||
root.confirmed()
|
root.confirmed()
|
||||||
@ -292,6 +293,28 @@ Dialog {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
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, temporarily slowing down the email download process significantly.")
|
||||||
|
title : qsTr("Disable early access")
|
||||||
|
answer : qsTr("Disabling early access...")
|
||||||
|
}
|
||||||
|
},
|
||||||
State {
|
State {
|
||||||
name: "noKeychain"
|
name: "noKeychain"
|
||||||
PropertyChanges {
|
PropertyChanges {
|
||||||
@ -340,13 +363,9 @@ Dialog {
|
|||||||
winMain.dialogAddUser .visible = false
|
winMain.dialogAddUser .visible = false
|
||||||
winMain.dialogChangePort .visible = false
|
winMain.dialogChangePort .visible = false
|
||||||
winMain.dialogCredits .visible = false
|
winMain.dialogCredits .visible = false
|
||||||
winMain.dialogVersionInfo .visible = false
|
|
||||||
// dialogFirstStart should reappear again after closing global
|
|
||||||
root.visible = true
|
root.visible = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
onConfirmed : {
|
onConfirmed : {
|
||||||
if (state == "quit" || state == "instance exists" ) {
|
if (state == "quit" || state == "instance exists" ) {
|
||||||
timer.interval = 1000
|
timer.interval = 1000
|
||||||
@ -360,17 +379,19 @@ Dialog {
|
|||||||
Connections {
|
Connections {
|
||||||
target: timer
|
target: timer
|
||||||
onTriggered: {
|
onTriggered: {
|
||||||
if ( state == "addressmode" ) { go.switchAddressMode (input) }
|
if ( state == "addressmode" ) { go.switchAddressMode (input) }
|
||||||
if ( state == "clearChain" ) { go.clearKeychain () }
|
if ( state == "clearChain" ) { go.clearKeychain () }
|
||||||
if ( state == "clearCache" ) { go.clearCache () }
|
if ( state == "clearCache" ) { go.clearCache () }
|
||||||
if ( state == "deleteUser" ) { go.deleteAccount (input, checkBoxWrapper.isChecked) }
|
if ( state == "deleteUser" ) { go.deleteAccount (input, checkBoxWrapper.isChecked) }
|
||||||
if ( state == "logout" ) { go.logoutAccount (input) }
|
if ( state == "logout" ) { go.logoutAccount (input) }
|
||||||
if ( state == "toggleAutoStart" ) { go.toggleAutoStart () }
|
if ( state == "toggleAutoStart" ) { go.toggleAutoStart () }
|
||||||
if ( state == "toggleAllowProxy" ) { go.toggleAllowProxy () }
|
if ( state == "toggleAllowProxy" ) { go.toggleAllowProxy () }
|
||||||
if ( state == "quit" ) { Qt.quit () }
|
if ( state == "toggleEarlyAccessOn" ) { go.toggleEarlyAccess () }
|
||||||
if ( state == "instance exists" ) { Qt.quit () }
|
if ( state == "toggleEarlyAccessOff" ) { go.toggleEarlyAccess () }
|
||||||
if ( state == "noKeychain" ) { Qt.quit () }
|
if ( state == "quit" ) { Qt.quit () }
|
||||||
if ( state == "checkUpdates" ) { go.runCheckVersion (true) }
|
if ( state == "instance exists" ) { Qt.quit () }
|
||||||
|
if ( state == "noKeychain" ) { Qt.quit () }
|
||||||
|
if ( state == "checkUpdates" ) { }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -74,9 +74,7 @@ Item {
|
|||||||
rightIcon.text : Style.fa.chevron_circle_right
|
rightIcon.text : Style.fa.chevron_circle_right
|
||||||
rightIcon.font.pointSize : Style.settings.toggleSize * Style.pt
|
rightIcon.font.pointSize : Style.settings.toggleSize * Style.pt
|
||||||
onClicked: {
|
onClicked: {
|
||||||
dialogGlobal.state="checkUpdates"
|
go.checkForUpdates()
|
||||||
dialogGlobal.show()
|
|
||||||
dialogGlobal.confirmed()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -137,10 +135,7 @@ Item {
|
|||||||
textColor : Style.main.textDisabled
|
textColor : Style.main.textDisabled
|
||||||
fontSize : Style.main.fontSize
|
fontSize : Style.main.fontSize
|
||||||
textUnderline : true
|
textUnderline : true
|
||||||
onClicked : {
|
onClicked : gui.openReleaseNotes()
|
||||||
go.getLocalVersionInfo()
|
|
||||||
winMain.dialogVersionInfo.show()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -38,7 +38,6 @@ Window {
|
|||||||
property alias dialogUpdate : dialogUpdate
|
property alias dialogUpdate : dialogUpdate
|
||||||
property alias dialogFirstStart : dialogFirstStart
|
property alias dialogFirstStart : dialogFirstStart
|
||||||
property alias dialogGlobal : dialogGlobal
|
property alias dialogGlobal : dialogGlobal
|
||||||
property alias dialogVersionInfo : dialogVersionInfo
|
|
||||||
property alias dialogConnectionTroubleshoot : dialogConnectionTroubleshoot
|
property alias dialogConnectionTroubleshoot : dialogConnectionTroubleshoot
|
||||||
property alias bubbleNote : bubbleNote
|
property alias bubbleNote : bubbleNote
|
||||||
property alias addAccountTip : addAccountTip
|
property alias addAccountTip : addAccountTip
|
||||||
@ -66,7 +65,6 @@ Window {
|
|||||||
!dialogUpdate .visible &&
|
!dialogUpdate .visible &&
|
||||||
!dialogFirstStart .visible &&
|
!dialogFirstStart .visible &&
|
||||||
!dialogGlobal .visible &&
|
!dialogGlobal .visible &&
|
||||||
!dialogVersionInfo .visible &&
|
|
||||||
!bubbleNote .visible
|
!bubbleNote .visible
|
||||||
|
|
||||||
Accessible.role: Accessible.Grouping
|
Accessible.role: Accessible.Grouping
|
||||||
@ -316,50 +314,7 @@ Window {
|
|||||||
|
|
||||||
DialogUpdate {
|
DialogUpdate {
|
||||||
id: dialogUpdate
|
id: dialogUpdate
|
||||||
|
forceUpdate: root.isOutdateVersion
|
||||||
property string manualLinks : {
|
|
||||||
var out = ""
|
|
||||||
var links = go.downloadLink.split("\n")
|
|
||||||
var l;
|
|
||||||
for (l in links) {
|
|
||||||
out += '<a href="%1">%1</a><br>'.arg(links[l])
|
|
||||||
}
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
title: root.isOutdateVersion ?
|
|
||||||
qsTr("%1 is outdated", "title of outdate dialog").arg(go.programTitle):
|
|
||||||
qsTr("%1 update to %2", "title of update dialog").arg(go.programTitle).arg(go.newversion)
|
|
||||||
introductionText: {
|
|
||||||
if (root.isOutdateVersion) {
|
|
||||||
if (go.goos=="linux") {
|
|
||||||
return qsTr('You are using an outdated version of our software.<br>
|
|
||||||
Please download and install the latest version to continue using %1.<br><br>
|
|
||||||
%2',
|
|
||||||
"Message for force-update in Linux").arg(go.programTitle).arg(dialogUpdate.manualLinks)
|
|
||||||
} else {
|
|
||||||
return qsTr('You are using an outdated version of our software.<br>
|
|
||||||
Please download and install the latest version to continue using %1.<br><br>
|
|
||||||
You can continue with the update or download and install the new version manually from<br><br>
|
|
||||||
<a href="%2">%2</a>',
|
|
||||||
"Message for force-update in Win/Mac").arg(go.programTitle).arg(go.landingPage)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (go.goos=="linux") {
|
|
||||||
return qsTr('A new version of Bridge is available.<br>
|
|
||||||
Check <a href="%1">release notes</a> to learn what is new in %2.<br>
|
|
||||||
Use your package manager to update or download and install the new version manually from<br><br>
|
|
||||||
%3',
|
|
||||||
"Message for update in Linux").arg("releaseNotes").arg(go.newversion).arg(dialogUpdate.manualLinks)
|
|
||||||
} else {
|
|
||||||
return qsTr('A new version of Bridge is available.<br>
|
|
||||||
Check <a href="%1">release notes</a> to learn what is new in %2.<br>
|
|
||||||
You can continue with the update or download and install the new version manually from<br><br>
|
|
||||||
<a href="%3">%3</a>',
|
|
||||||
"Message for update in Win/Mac").arg("releaseNotes").arg(go.newversion).arg(go.landingPage)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -373,25 +328,6 @@ Window {
|
|||||||
id: dialogTlsCert
|
id: dialogTlsCert
|
||||||
}
|
}
|
||||||
|
|
||||||
Dialog {
|
|
||||||
id: dialogVersionInfo
|
|
||||||
property bool checkVersionOnClose : false
|
|
||||||
title: qsTr("Information about", "title of release notes page") + " v" + go.newversion
|
|
||||||
VersionInfo { }
|
|
||||||
onShow : {
|
|
||||||
// Hide information bar with old version
|
|
||||||
if (infoBar.state=="oldVersion") {
|
|
||||||
infoBar.state="upToDate"
|
|
||||||
dialogVersionInfo.checkVersionOnClose = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
onHide : {
|
|
||||||
// Reload current version based on online status
|
|
||||||
if (dialogVersionInfo.checkVersionOnClose) go.runCheckVersion(false)
|
|
||||||
dialogVersionInfo.checkVersionOnClose = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DialogYesNo {
|
DialogYesNo {
|
||||||
id: dialogGlobal
|
id: dialogGlobal
|
||||||
question : ""
|
question : ""
|
||||||
|
|||||||
@ -36,6 +36,24 @@ Item {
|
|||||||
color: Style.main.background
|
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
|
// content
|
||||||
Column {
|
Column {
|
||||||
anchors.left : parent.left
|
anchors.left : parent.left
|
||||||
@ -97,6 +115,50 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
ButtonIconText {
|
||||||
id: advancedSettings
|
id: advancedSettings
|
||||||
property bool isAdvanced : !go.isDefaultPort
|
property bool isAdvanced : !go.isDefaultPort
|
||||||
@ -177,7 +239,6 @@ Item {
|
|||||||
dialogGlobal.show()
|
dialogGlobal.show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,127 +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/>.
|
|
||||||
|
|
||||||
// credits
|
|
||||||
|
|
||||||
import QtQuick 2.8
|
|
||||||
import BridgeUI 1.0
|
|
||||||
import ProtonUI 1.0
|
|
||||||
|
|
||||||
Item {
|
|
||||||
Rectangle {
|
|
||||||
id: wrapper
|
|
||||||
anchors.centerIn: parent
|
|
||||||
width: 2*Style.main.width/3
|
|
||||||
height: Style.main.height - 6*Style.dialog.titleSize
|
|
||||||
color: "transparent"
|
|
||||||
|
|
||||||
Flickable {
|
|
||||||
anchors.fill : wrapper
|
|
||||||
contentWidth : wrapper.width
|
|
||||||
contentHeight : content.height
|
|
||||||
flickableDirection : Flickable.VerticalFlick
|
|
||||||
clip : true
|
|
||||||
|
|
||||||
|
|
||||||
Column {
|
|
||||||
id: content
|
|
||||||
anchors.top: parent.top
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
width: wrapper.width
|
|
||||||
spacing: Style.dialog.spacing
|
|
||||||
|
|
||||||
AccessibleText {
|
|
||||||
visible: go.changelog != ""
|
|
||||||
anchors {
|
|
||||||
left: parent.left
|
|
||||||
}
|
|
||||||
font.bold: true
|
|
||||||
font.pointSize: Style.main.fontSize * Style.pt
|
|
||||||
color: Style.main.text
|
|
||||||
text: qsTr("Release notes", "list of release notes for this version of the app") + ":"
|
|
||||||
}
|
|
||||||
|
|
||||||
AccessibleSelectableText {
|
|
||||||
anchors {
|
|
||||||
left: parent.left
|
|
||||||
leftMargin: Style.main.leftMargin
|
|
||||||
}
|
|
||||||
font {
|
|
||||||
pointSize : Style.main.fontSize * Style.pt
|
|
||||||
}
|
|
||||||
width: wrapper.width - anchors.leftMargin
|
|
||||||
onLinkActivated: {
|
|
||||||
Qt.openUrlExternally(link)
|
|
||||||
}
|
|
||||||
wrapMode: Text.Wrap
|
|
||||||
color: Style.main.text
|
|
||||||
text: go.changelog
|
|
||||||
}
|
|
||||||
|
|
||||||
AccessibleText {
|
|
||||||
visible: go.bugfixes != ""
|
|
||||||
anchors {
|
|
||||||
left: parent.left
|
|
||||||
}
|
|
||||||
font.bold: true
|
|
||||||
font.pointSize: Style.main.fontSize * Style.pt
|
|
||||||
color: Style.main.text
|
|
||||||
text: qsTr("Fixed bugs", "list of bugs fixed for this version of the app") + ":"
|
|
||||||
}
|
|
||||||
|
|
||||||
AccessibleSelectableText {
|
|
||||||
visible: go.bugfixes!=""
|
|
||||||
anchors {
|
|
||||||
left: parent.left
|
|
||||||
leftMargin: Style.main.leftMargin
|
|
||||||
}
|
|
||||||
font {
|
|
||||||
pointSize : Style.main.fontSize * Style.pt
|
|
||||||
}
|
|
||||||
width: wrapper.width - anchors.leftMargin
|
|
||||||
onLinkActivated: {
|
|
||||||
Qt.openUrlExternally(link)
|
|
||||||
}
|
|
||||||
wrapMode: Text.Wrap
|
|
||||||
color: Style.main.text
|
|
||||||
text: go.bugfixes
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle{id:spacer; color:Style.transparent; width: Style.main.dummy; height: buttonClose.height}
|
|
||||||
|
|
||||||
ButtonRounded {
|
|
||||||
id: buttonClose
|
|
||||||
anchors.horizontalCenter: content.horizontalCenter
|
|
||||||
text: qsTr("Close")
|
|
||||||
onClicked: {
|
|
||||||
dialogVersionInfo.hide()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
AccessibleSelectableText {
|
|
||||||
anchors.horizontalCenter: content.horizontalCenter
|
|
||||||
font {
|
|
||||||
pointSize : Style.main.fontSize * Style.pt
|
|
||||||
}
|
|
||||||
color: Style.main.textDisabled
|
|
||||||
text: "\n Current: "+go.fullversion
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -12,4 +12,3 @@ ManualWindow 1.0 ManualWindow.qml
|
|||||||
OutgoingNoEncPopup 1.0 OutgoingNoEncPopup.qml
|
OutgoingNoEncPopup 1.0 OutgoingNoEncPopup.qml
|
||||||
SettingsView 1.0 SettingsView.qml
|
SettingsView 1.0 SettingsView.qml
|
||||||
StatusFooter 1.0 StatusFooter.qml
|
StatusFooter 1.0 StatusFooter.qml
|
||||||
VersionInfo 1.0 VersionInfo.qml
|
|
||||||
|
|||||||
@ -54,13 +54,15 @@ Item {
|
|||||||
onWarningFlagsChanged : {
|
onWarningFlagsChanged : {
|
||||||
if (gui.warningFlags==Style.okInfoBar) {
|
if (gui.warningFlags==Style.okInfoBar) {
|
||||||
go.normalSystray()
|
go.normalSystray()
|
||||||
} else {
|
return
|
||||||
if ((gui.warningFlags & Style.errorInfoBar) == Style.errorInfoBar) {
|
|
||||||
go.errorSystray()
|
|
||||||
} else {
|
|
||||||
go.highlightSystray()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ((gui.warningFlags & Style.errorInfoBar) == Style.errorInfoBar) {
|
||||||
|
go.errorSystray()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
go.highlightSystray()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Signals from Go
|
// Signals from Go
|
||||||
@ -112,14 +114,6 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onRunCheckVersion : {
|
|
||||||
gui.openMainWindow(false)
|
|
||||||
go.setUpdateState("upToDate")
|
|
||||||
winMain.dialogGlobal.state="checkUpdates"
|
|
||||||
winMain.dialogGlobal.show()
|
|
||||||
go.isNewVersionAvailable(showMessage)
|
|
||||||
}
|
|
||||||
|
|
||||||
onSetUpdateState : {
|
onSetUpdateState : {
|
||||||
// once app is outdated prevent from state change
|
// once app is outdated prevent from state change
|
||||||
if (winMain.updateState != "forceUpdate") {
|
if (winMain.updateState != "forceUpdate") {
|
||||||
@ -134,15 +128,49 @@ Item {
|
|||||||
go.silentBubble(2,qsTr("You have the latest version!", "notification", -1))
|
go.silentBubble(2,qsTr("You have the latest version!", "notification", -1))
|
||||||
}
|
}
|
||||||
|
|
||||||
onNotifyUpdate : {
|
onNotifyManualUpdate: {
|
||||||
|
go.setUpdateState("oldVersion")
|
||||||
|
}
|
||||||
|
|
||||||
|
onNotifyManualUpdateRestartNeeded: {
|
||||||
|
if (!winMain.dialogUpdate.visible) {
|
||||||
|
gui.openMainWindow(true)
|
||||||
|
winMain.dialogUpdate.show()
|
||||||
|
}
|
||||||
|
go.setUpdateState("updateRestart")
|
||||||
|
winMain.dialogUpdate.finished(false)
|
||||||
|
|
||||||
|
// after manual update - just retart immidiatly
|
||||||
|
go.setToRestart()
|
||||||
|
Qt.quit()
|
||||||
|
}
|
||||||
|
|
||||||
|
onNotifyManualUpdateError: {
|
||||||
|
if (!winMain.dialogUpdate.visible) {
|
||||||
|
gui.openMainWindow(true)
|
||||||
|
winMain.dialogUpdate.show()
|
||||||
|
}
|
||||||
|
go.setUpdateState("updateError")
|
||||||
|
winMain.dialogUpdate.finished(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
onNotifyForceUpdate : {
|
||||||
go.setUpdateState("forceUpdate")
|
go.setUpdateState("forceUpdate")
|
||||||
if (!winMain.dialogUpdate.visible) {
|
if (!winMain.dialogUpdate.visible) {
|
||||||
gui.openMainWindow(true)
|
gui.openMainWindow(true)
|
||||||
go.runCheckVersion(false)
|
|
||||||
winMain.dialogUpdate.show()
|
winMain.dialogUpdate.show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onNotifySilentUpdateRestartNeeded: {
|
||||||
|
go.setUpdateState("updateRestart")
|
||||||
|
}
|
||||||
|
|
||||||
|
onNotifySilentUpdateError: {
|
||||||
|
go.setUpdateState("updateError")
|
||||||
|
gui.openMainWindow(true)
|
||||||
|
}
|
||||||
|
|
||||||
onNotifyLogout : {
|
onNotifyLogout : {
|
||||||
go.notifyBubble(0, qsTr("Account %1 has been disconnected. Please log in to continue to use the Bridge with this account.").arg(accname) )
|
go.notifyBubble(0, qsTr("Account %1 has been disconnected. Please log in to continue to use the Bridge with this account.").arg(accname) )
|
||||||
}
|
}
|
||||||
@ -229,25 +257,16 @@ Item {
|
|||||||
outgoingNoEncPopup.y = y
|
outgoingNoEncPopup.y = y
|
||||||
}
|
}
|
||||||
|
|
||||||
onUpdateFinished : {
|
|
||||||
winMain.dialogUpdate.finished(hasError)
|
|
||||||
}
|
|
||||||
|
|
||||||
onShowCertIssue : {
|
onShowCertIssue : {
|
||||||
winMain.tlsBarState="notOK"
|
winMain.tlsBarState="notOK"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onOpenReleaseNotesExternally: {
|
||||||
|
Qt.openUrlExternally(go.updateReleaseNotesLink)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Timer {
|
|
||||||
id: checkVersionTimer
|
|
||||||
repeat : true
|
|
||||||
triggeredOnStart: false
|
|
||||||
interval : Style.main.verCheckRepeatTime
|
|
||||||
onTriggered : go.runCheckVersion(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
function openMainWindow(showAndRise) {
|
function openMainWindow(showAndRise) {
|
||||||
// wait and check until font is loaded
|
// wait and check until font is loaded
|
||||||
while(true){
|
while(true){
|
||||||
@ -287,6 +306,15 @@ Item {
|
|||||||
winMain.bubbleNote.show()
|
winMain.bubbleNote.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function openReleaseNotes(){
|
||||||
|
if (go.updateReleaseNotesLink == "") {
|
||||||
|
go.checkAndOpenReleaseNotes()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
go.openReleaseNotesExternally()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// On start
|
// On start
|
||||||
Component.onCompleted : {
|
Component.onCompleted : {
|
||||||
// set messages for translations
|
// set messages for translations
|
||||||
@ -299,17 +327,12 @@ Item {
|
|||||||
go.failedAutostart = qsTr("Unable to configure automatic start." , "notification", -1)
|
go.failedAutostart = qsTr("Unable to configure automatic start." , "notification", -1)
|
||||||
go.genericErrSeeLogs = qsTr("An error happened during procedure. See logs for more details." , "notification", -1)
|
go.genericErrSeeLogs = qsTr("An error happened during procedure. See logs for more details." , "notification", -1)
|
||||||
|
|
||||||
|
go.guiIsReady()
|
||||||
|
|
||||||
// start window
|
// start window
|
||||||
gui.openMainWindow(false)
|
gui.openMainWindow(false)
|
||||||
checkVersionTimer.start()
|
|
||||||
if (go.isShownOnStart) {
|
if (go.isShownOnStart) {
|
||||||
gui.winMain.showAndRise()
|
gui.winMain.showAndRise()
|
||||||
}
|
}
|
||||||
go.runCheckVersion(false)
|
|
||||||
|
|
||||||
if (go.isFreshVersion) {
|
|
||||||
go.getLocalVersionInfo()
|
|
||||||
gui.winMain.dialogVersionInfo.show()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -38,7 +38,7 @@ Item {
|
|||||||
property var allMonths : getMonthList(1,12)
|
property var allMonths : getMonthList(1,12)
|
||||||
property var allDays : getDayList(1,31)
|
property var allDays : getDayList(1,31)
|
||||||
|
|
||||||
property var enums : JSON.parse('{"pathOK":1,"pathEmptyPath":2,"pathWrongPath":4,"pathNotADir":8,"pathWrongPermissions":16,"pathDirEmpty":32,"errUnknownError":0,"errEventAPILogout":1,"errUpdateAPI":2,"errUpdateJSON":3,"errUserAuth":4,"errQApplication":18,"errEmailExportFailed":6,"errEmailExportMissing":7,"errNothingToImport":8,"errEmailImportFailed":12,"errDraftImportFailed":13,"errDraftLabelFailed":14,"errEncryptMessageAttachment":15,"errEncryptMessage":16,"errNoInternetWhileImport":17,"errUnlockUser":5,"errSourceMessageNotSelected":19,"errCannotParseMail":5000,"errWrongLoginOrPassword":5001,"errWrongServerPathOrPort":5002,"errWrongAuthMethod":5003,"errIMAPFetchFailed":5004,"errLocalSourceLoadFailed":1000,"errPMLoadFailed":1001,"errRemoteSourceLoadFailed":1002,"errLoadAccountList":1005,"errExit":1006,"errRetry":1007,"errAsk":1008,"errImportFailed":1009,"errCreateLabelFailed":1010,"errCreateFolderFailed":1011,"errUpdateLabelFailed":1012,"errUpdateFolderFailed":1013,"errFillFolderName":1014,"errSelectFolderColor":1015,"errNoInternet":1016,"folderTypeSystem":"system","folderTypeLabel":"label","folderTypeFolder":"folder","folderTypeExternal":"external","progressInit":"init","progressLooping":"looping","statusNoInternet":"noInternet","statusCheckingInternet":"internetCheck","statusNewVersionAvailable":"oldVersion","statusUpToDate":"upToDate","statusForceUpdate":"forceupdate"}')
|
property var enums : JSON.parse('{"pathOK":1,"pathEmptyPath":2,"pathWrongPath":4,"pathNotADir":8,"pathWrongPermissions":16,"pathDirEmpty":32,"errUnknownError":0,"errEventAPILogout":1,"errUpdateAPI":2,"errUpdateJSON":3,"errUserAuth":4,"errQApplication":18,"errEmailExportFailed":6,"errEmailExportMissing":7,"errNothingToImport":8,"errEmailImportFailed":12,"errDraftImportFailed":13,"errDraftLabelFailed":14,"errEncryptMessageAttachment":15,"errEncryptMessage":16,"errNoInternetWhileImport":17,"errUnlockUser":5,"errSourceMessageNotSelected":19,"errCannotParseMail":5000,"errWrongLoginOrPassword":5001,"errWrongServerPathOrPort":5002,"errWrongAuthMethod":5003,"errIMAPFetchFailed":5004,"errLocalSourceLoadFailed":1000,"errPMLoadFailed":1001,"errRemoteSourceLoadFailed":1002,"errLoadAccountList":1005,"errExit":1006,"errRetry":1007,"errAsk":1008,"errImportFailed":1009,"errCreateLabelFailed":1010,"errCreateFolderFailed":1011,"errUpdateLabelFailed":1012,"errUpdateFolderFailed":1013,"errFillFolderName":1014,"errSelectFolderColor":1015,"errNoInternet":1016,"folderTypeSystem":"system","folderTypeLabel":"label","folderTypeFolder":"folder","folderTypeExternal":"external","progressInit":"init","progressLooping":"looping","statusNoInternet":"noInternet","statusCheckingInternet":"internetCheck","statusNewVersionAvailable":"oldVersion","statusUpToDate":"upToDate","statusForceUpdate":"forceUpdate"}')
|
||||||
|
|
||||||
IEStyle{}
|
IEStyle{}
|
||||||
|
|
||||||
@ -69,20 +69,9 @@ Item {
|
|||||||
Connections {
|
Connections {
|
||||||
target: go
|
target: go
|
||||||
|
|
||||||
|
onShowWindow : {
|
||||||
|
winMain.showAndRise()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
onProcessFinished : {
|
onProcessFinished : {
|
||||||
winMain.dialogAddUser.hide()
|
winMain.dialogAddUser.hide()
|
||||||
@ -114,16 +103,9 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onRunCheckVersion : {
|
|
||||||
go.setUpdateState(gui.enums.statusUpToDate)
|
|
||||||
winMain.dialogGlobal.state=gui.enums.statusCheckingInternet
|
|
||||||
winMain.dialogGlobal.show()
|
|
||||||
go.isNewVersionAvailable(showMessage)
|
|
||||||
}
|
|
||||||
|
|
||||||
onSetUpdateState : {
|
onSetUpdateState : {
|
||||||
// once app is outdated prevent from state change
|
// once app is outdated prevent from state change
|
||||||
if (winMain.updateState != gui.enums.statusForceUpdate) {
|
if (winMain.updateState != "forceUpdate") {
|
||||||
winMain.updateState = updateState
|
winMain.updateState = updateState
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -224,13 +206,43 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onNotifyUpdate : {
|
onNotifyManualUpdate: {
|
||||||
go.setUpdateState("forceUpdate")
|
go.setUpdateState("oldVersion")
|
||||||
|
}
|
||||||
|
|
||||||
|
onNotifyManualUpdateRestartNeeded: {
|
||||||
if (!winMain.dialogUpdate.visible) {
|
if (!winMain.dialogUpdate.visible) {
|
||||||
gui.openMainWindow(true)
|
|
||||||
go.runCheckVersion(false)
|
|
||||||
winMain.dialogUpdate.show()
|
winMain.dialogUpdate.show()
|
||||||
}
|
}
|
||||||
|
go.setUpdateState("updateRestart")
|
||||||
|
winMain.dialogUpdate.finished(false)
|
||||||
|
|
||||||
|
// after manual update - just retart immidiatly
|
||||||
|
go.setToRestart()
|
||||||
|
Qt.quit()
|
||||||
|
}
|
||||||
|
|
||||||
|
onNotifyManualUpdateError: {
|
||||||
|
if (!winMain.dialogUpdate.visible) {
|
||||||
|
winMain.dialogUpdate.show()
|
||||||
|
}
|
||||||
|
go.setUpdateState("updateError")
|
||||||
|
winMain.dialogUpdate.finished(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
onNotifyForceUpdate : {
|
||||||
|
go.setUpdateState("forceUpdate")
|
||||||
|
if (!winMain.dialogUpdate.visible) {
|
||||||
|
winMain.dialogUpdate.show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onNotifySilentUpdateRestartNeeded: {
|
||||||
|
go.setUpdateState("updateRestart")
|
||||||
|
}
|
||||||
|
|
||||||
|
onNotifySilentUpdateError: {
|
||||||
|
go.setUpdateState("updateError")
|
||||||
}
|
}
|
||||||
|
|
||||||
onNotifyLogout : {
|
onNotifyLogout : {
|
||||||
@ -280,6 +292,12 @@ Item {
|
|||||||
onUpdateFinished : {
|
onUpdateFinished : {
|
||||||
winMain.dialogUpdate.finished(hasError)
|
winMain.dialogUpdate.finished(hasError)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onOpenReleaseNotesExternally: {
|
||||||
|
Qt.openUrlExternally(go.updateReleaseNotesLink)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function folderIcon(folderName, folderType) { // translations
|
function folderIcon(folderName, folderType) { // translations
|
||||||
@ -393,14 +411,15 @@ Item {
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Timer {
|
function openReleaseNotes(){
|
||||||
id: checkVersionTimer
|
if (go.updateReleaseNotesLink == "") {
|
||||||
repeat : true
|
go.checkAndOpenReleaseNotes()
|
||||||
triggeredOnStart: false
|
return
|
||||||
interval : Style.main.verCheckRepeatTime
|
}
|
||||||
onTriggered : go.runCheckVersion(false)
|
go.openReleaseNotesExternally()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
property string areYouSureYouWantToQuit : qsTr("There are incomplete processes - some items are not yet transferred. Do you really want to stop and quit?")
|
property string areYouSureYouWantToQuit : qsTr("There are incomplete processes - some items are not yet transferred. Do you really want to stop and quit?")
|
||||||
// On start
|
// On start
|
||||||
Component.onCompleted : {
|
Component.onCompleted : {
|
||||||
@ -413,8 +432,8 @@ Item {
|
|||||||
go.bugNotSent = qsTr("Unable to submit bug report." , "notification", -1)
|
go.bugNotSent = qsTr("Unable to submit bug report." , "notification", -1)
|
||||||
go.bugReportSent = qsTr("Bug report successfully sent." , "notification", -1)
|
go.bugReportSent = qsTr("Bug report successfully sent." , "notification", -1)
|
||||||
|
|
||||||
go.runCheckVersion(false)
|
|
||||||
checkVersionTimer.start()
|
go.guiIsReady()
|
||||||
|
|
||||||
gui.allMonths = getMonthList(1,12)
|
gui.allMonths = getMonthList(1,12)
|
||||||
gui.allMonthsChanged()
|
gui.allMonthsChanged()
|
||||||
|
|||||||
@ -314,7 +314,6 @@ Dialog {
|
|||||||
// hide all other dialogs
|
// hide all other dialogs
|
||||||
winMain.dialogAddUser .visible = false
|
winMain.dialogAddUser .visible = false
|
||||||
winMain.dialogCredits .visible = false
|
winMain.dialogCredits .visible = false
|
||||||
//winMain.dialogVersionInfo .visible = false
|
|
||||||
// dialogFirstStart should reappear again after closing global
|
// dialogFirstStart should reappear again after closing global
|
||||||
root.visible = true
|
root.visible = true
|
||||||
}
|
}
|
||||||
@ -342,7 +341,7 @@ Dialog {
|
|||||||
if ( state == "toggleAutoStart" ) { go.toggleAutoStart () }
|
if ( state == "toggleAutoStart" ) { go.toggleAutoStart () }
|
||||||
if ( state == "quit" ) { Qt.quit () }
|
if ( state == "quit" ) { Qt.quit () }
|
||||||
if ( state == "instance exists" ) { Qt.quit () }
|
if ( state == "instance exists" ) { Qt.quit () }
|
||||||
if ( state == "checkUpdates" ) { go.runCheckVersion (true) }
|
if ( state == "checkUpdates" ) { }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -55,9 +55,7 @@ Item {
|
|||||||
rightIcon.text : Style.fa.chevron_circle_right
|
rightIcon.text : Style.fa.chevron_circle_right
|
||||||
rightIcon.font.pointSize : Style.settings.toggleSize * Style.pt
|
rightIcon.font.pointSize : Style.settings.toggleSize * Style.pt
|
||||||
onClicked: {
|
onClicked: {
|
||||||
dialogGlobal.state="checkUpdates"
|
go.checkForUpdates()
|
||||||
dialogGlobal.show()
|
|
||||||
dialogGlobal.confirmed()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -115,7 +113,7 @@ Item {
|
|||||||
MouseArea {
|
MouseArea {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
onClicked : {
|
onClicked : {
|
||||||
go.openLicenseFile()
|
go.openLicenseFile()
|
||||||
}
|
}
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
}
|
}
|
||||||
@ -129,10 +127,7 @@ Item {
|
|||||||
font.underline: true
|
font.underline: true
|
||||||
MouseArea {
|
MouseArea {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
onClicked : {
|
onClicked : gui.openReleaseNotes()
|
||||||
go.getLocalVersionInfo()
|
|
||||||
winMain.dialogVersionInfo.show()
|
|
||||||
}
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -34,7 +34,6 @@ Window {
|
|||||||
property alias dialogAddUser : dialogAddUser
|
property alias dialogAddUser : dialogAddUser
|
||||||
property alias dialogGlobal : dialogGlobal
|
property alias dialogGlobal : dialogGlobal
|
||||||
property alias dialogCredits : dialogCredits
|
property alias dialogCredits : dialogCredits
|
||||||
property alias dialogVersionInfo : dialogVersionInfo
|
|
||||||
property alias dialogUpdate : dialogUpdate
|
property alias dialogUpdate : dialogUpdate
|
||||||
property alias popupMessage : popupMessage
|
property alias popupMessage : popupMessage
|
||||||
property alias popupFolderEdit : popupFolderEdit
|
property alias popupFolderEdit : popupFolderEdit
|
||||||
@ -56,12 +55,11 @@ Window {
|
|||||||
minimumWidth : Style.main.width
|
minimumWidth : Style.main.width
|
||||||
minimumHeight : Style.main.height
|
minimumHeight : Style.main.height
|
||||||
|
|
||||||
property bool isOutdateVersion : root.updateState == "forceUpgrade"
|
property bool isOutdateVersion : root.updateState == "forceUpdate"
|
||||||
|
|
||||||
property bool activeContent :
|
property bool activeContent :
|
||||||
!dialogAddUser .visible &&
|
!dialogAddUser .visible &&
|
||||||
!dialogCredits .visible &&
|
!dialogCredits .visible &&
|
||||||
!dialogVersionInfo .visible &&
|
|
||||||
!dialogGlobal .visible &&
|
!dialogGlobal .visible &&
|
||||||
!dialogUpdate .visible &&
|
!dialogUpdate .visible &&
|
||||||
!dialogImport .visible &&
|
!dialogImport .visible &&
|
||||||
@ -254,40 +252,7 @@ Window {
|
|||||||
|
|
||||||
DialogUpdate {
|
DialogUpdate {
|
||||||
id: dialogUpdate
|
id: dialogUpdate
|
||||||
|
forceUpdate: root.isOutdateVersion
|
||||||
title: root.isOutdateVersion ?
|
|
||||||
qsTr("%1 is outdated", "title of outdate dialog").arg(go.programTitle):
|
|
||||||
qsTr("%1 update to %2", "title of update dialog").arg(go.programTitle).arg(go.newversion)
|
|
||||||
introductionText: {
|
|
||||||
if (root.isOutdateVersion) {
|
|
||||||
if (go.goos=="linux") {
|
|
||||||
return qsTr('You are using an outdated version of our software.<br>
|
|
||||||
Please dowload and install the latest version to continue using %1.<br><br>
|
|
||||||
<a href="%2">%2</a>',
|
|
||||||
"Message for force-update in Linux").arg(go.programTitle).arg(go.landingPage)
|
|
||||||
} else {
|
|
||||||
return qsTr('You are using an outdated version of our software.<br>
|
|
||||||
Please dowload and install the latest version to continue using %1.<br><br>
|
|
||||||
You can continue with update or download and install the new version manually from<br><br>
|
|
||||||
<a href="%2">%2</a>',
|
|
||||||
"Message for force-update in Win/Mac").arg(go.programTitle).arg(go.landingPage)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (go.goos=="linux") {
|
|
||||||
return qsTr('A new version of %1 is available.<br>
|
|
||||||
Check <a href="%2">release notes</a> to learn what is new in %3.<br>
|
|
||||||
Use your package manager to update or download and install new the version manually from<br><br>
|
|
||||||
<a href="%4">%4</a>',
|
|
||||||
"Message for update in Linux").arg(go.programTitle).arg("releaseNotes").arg(go.newversion).arg(go.landingPage)
|
|
||||||
} else {
|
|
||||||
return qsTr('A new version of %1 is available.<br>
|
|
||||||
Check <a href="%2">release notes</a> to learn what is new in %3.<br>
|
|
||||||
You can continue with update or download and install new the version manually from<br><br>
|
|
||||||
<a href="%4">%4</a>',
|
|
||||||
"Message for update in Win/Mac").arg(go.programTitle).arg("releaseNotes").arg(go.newversion).arg(go.landingPage)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -327,31 +292,6 @@ Window {
|
|||||||
Credits { }
|
Credits { }
|
||||||
}
|
}
|
||||||
|
|
||||||
Dialog {
|
|
||||||
id: dialogVersionInfo
|
|
||||||
anchors {
|
|
||||||
top : infoBar.bottom
|
|
||||||
bottomMargin: innerWindowBorder
|
|
||||||
leftMargin: innerWindowBorder
|
|
||||||
rightMargin: innerWindowBorder
|
|
||||||
}
|
|
||||||
property bool checkVersion : false
|
|
||||||
title: qsTr("Information about", "title of release notes page") + " v" + go.newversion
|
|
||||||
VersionInfo { }
|
|
||||||
onShow : {
|
|
||||||
// Hide information bar with olde version
|
|
||||||
if ( infoBar.state=="oldVersion" ) {
|
|
||||||
infoBar.state="upToDate"
|
|
||||||
dialogVersionInfo.checkVersion = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
onHide : {
|
|
||||||
// Reload current version based on online status
|
|
||||||
if (dialogVersionInfo.checkVersion) go.runCheckVersion(false)
|
|
||||||
dialogVersionInfo.checkVersion = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DialogYesNo {
|
DialogYesNo {
|
||||||
id: dialogGlobal
|
id: dialogGlobal
|
||||||
question : ""
|
question : ""
|
||||||
|
|||||||
@ -20,17 +20,38 @@
|
|||||||
import QtQuick 2.8
|
import QtQuick 2.8
|
||||||
import ProtonUI 1.0
|
import ProtonUI 1.0
|
||||||
import ImportExportUI 1.0
|
import ImportExportUI 1.0
|
||||||
|
import QtQuick.Controls 2.4
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
// must have wrapper
|
// must have wrapper
|
||||||
Rectangle {
|
ScrollView {
|
||||||
id: wrapper
|
id: wrapper
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: parent.height
|
height: parent.height
|
||||||
color: Style.main.background
|
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
|
// content
|
||||||
Column {
|
Column {
|
||||||
@ -75,6 +96,25 @@ Item {
|
|||||||
onClicked: bugreportWin.show()
|
onClicked: bugreportWin.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
ButtonIconText {
|
||||||
|
|||||||
@ -1,125 +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/>.
|
|
||||||
|
|
||||||
// credits
|
|
||||||
|
|
||||||
import QtQuick 2.8
|
|
||||||
import ProtonUI 1.0
|
|
||||||
import ImportExportUI 1.0
|
|
||||||
|
|
||||||
Item {
|
|
||||||
id: root
|
|
||||||
Rectangle {
|
|
||||||
id: wrapper
|
|
||||||
anchors.centerIn: parent
|
|
||||||
width: 2*Style.main.width/3
|
|
||||||
height: Style.main.height - 6*Style.dialog.titleSize
|
|
||||||
color: "transparent"
|
|
||||||
|
|
||||||
Flickable {
|
|
||||||
anchors.fill : wrapper
|
|
||||||
contentWidth : wrapper.width
|
|
||||||
contentHeight : content.height
|
|
||||||
flickableDirection : Flickable.VerticalFlick
|
|
||||||
clip : true
|
|
||||||
|
|
||||||
|
|
||||||
Column {
|
|
||||||
id: content
|
|
||||||
anchors.top: parent.top
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
width: wrapper.width
|
|
||||||
spacing: 5
|
|
||||||
|
|
||||||
Text {
|
|
||||||
visible: go.changelog != ""
|
|
||||||
anchors {
|
|
||||||
left: parent.left
|
|
||||||
}
|
|
||||||
font.bold: true
|
|
||||||
font.pointSize: Style.main.fontSize * Style.pt
|
|
||||||
color: Style.main.text
|
|
||||||
text: qsTr("Release notes:")
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
|
||||||
anchors {
|
|
||||||
left: parent.left
|
|
||||||
leftMargin: Style.main.leftMargin
|
|
||||||
}
|
|
||||||
font.pointSize: Style.main.fontSize * Style.pt
|
|
||||||
width: wrapper.width - anchors.leftMargin
|
|
||||||
wrapMode: Text.Wrap
|
|
||||||
color: Style.main.text
|
|
||||||
text: go.changelog
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
|
||||||
visible: go.bugfixes != ""
|
|
||||||
anchors {
|
|
||||||
left: parent.left
|
|
||||||
}
|
|
||||||
font.bold: true
|
|
||||||
font.pointSize: Style.main.fontSize * Style.pt
|
|
||||||
color: Style.main.text
|
|
||||||
text: qsTr("Fixed bugs:")
|
|
||||||
}
|
|
||||||
|
|
||||||
Repeater {
|
|
||||||
anchors.fill: parent
|
|
||||||
model: go.bugfixes.split(";")
|
|
||||||
|
|
||||||
Text {
|
|
||||||
visible: go.bugfixes!=""
|
|
||||||
anchors {
|
|
||||||
left: parent.left
|
|
||||||
leftMargin: Style.main.leftMargin
|
|
||||||
}
|
|
||||||
font.pointSize: Style.main.fontSize * Style.pt
|
|
||||||
width: wrapper.width - anchors.leftMargin
|
|
||||||
wrapMode: Text.Wrap
|
|
||||||
color: Style.main.text
|
|
||||||
text: modelData
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle{id:spacer; color:"transparent"; width:10; height: buttonClose.height}
|
|
||||||
|
|
||||||
|
|
||||||
ButtonRounded {
|
|
||||||
id: buttonClose
|
|
||||||
anchors.horizontalCenter: content.horizontalCenter
|
|
||||||
text: "Close"
|
|
||||||
onClicked: {
|
|
||||||
root.parent.hide()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
AccessibleSelectableText {
|
|
||||||
anchors.horizontalCenter: content.horizontalCenter
|
|
||||||
font {
|
|
||||||
pointSize : Style.main.fontSize * Style.pt
|
|
||||||
}
|
|
||||||
color: Style.main.textDisabled
|
|
||||||
text: "\n Current: "+go.fullversion
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@ -38,6 +38,14 @@ StackLayout {
|
|||||||
visible: root.visible
|
visible: root.visible
|
||||||
z: -1
|
z: -1
|
||||||
|
|
||||||
|
// Looks like StackLayout explicatly sets visible=false to all viasual children except selected.
|
||||||
|
// We want this background to be also visible.
|
||||||
|
onVisibleChanged: {
|
||||||
|
if (visible != parent.visible) {
|
||||||
|
visible = parent.visible
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
AccessibleText {
|
AccessibleText {
|
||||||
id: titleText
|
id: titleText
|
||||||
anchors {
|
anchors {
|
||||||
|
|||||||
@ -25,16 +25,17 @@ import ProtonUI 1.0
|
|||||||
Dialog {
|
Dialog {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
title: "Bridge update "+go.newversion
|
|
||||||
|
|
||||||
property alias introductionText : introduction.text
|
|
||||||
property bool hasError : false
|
property bool hasError : false
|
||||||
|
property bool forceUpdate : false
|
||||||
|
|
||||||
signal cancel()
|
signal cancel()
|
||||||
signal okay()
|
signal okay()
|
||||||
|
|
||||||
|
title: forceUpdate ?
|
||||||
|
qsTr("Update %1 now", "title of force update dialog").arg(go.programTitle):
|
||||||
|
qsTr("Update to %1 %2", "title of normal update dialog").arg(go.programTitle).arg(go.updateVersion)
|
||||||
|
|
||||||
isDialogBusy: currentIndex==1
|
isDialogBusy: currentIndex==1 || forceUpdate
|
||||||
|
|
||||||
Rectangle { // 0: Release notes and confirm
|
Rectangle { // 0: Release notes and confirm
|
||||||
width: parent.width
|
width: parent.width
|
||||||
@ -51,24 +52,46 @@ Dialog {
|
|||||||
color: Style.dialog.text
|
color: Style.dialog.text
|
||||||
linkColor: Style.dialog.textBlue
|
linkColor: Style.dialog.textBlue
|
||||||
font {
|
font {
|
||||||
pointSize: 0.8 * Style.dialog.fontSize * Style.pt
|
pointSize: Style.dialog.fontSize * Style.pt
|
||||||
}
|
}
|
||||||
width: 2*root.width/3
|
width: 2*root.width/3
|
||||||
horizontalAlignment: Text.AlignHCenter
|
horizontalAlignment: Text.AlignHCenter
|
||||||
wrapMode: Text.Wrap
|
wrapMode: Text.Wrap
|
||||||
|
|
||||||
// customize message per application
|
text: {
|
||||||
text: ' <a href="%1">Release notes</a><br> New version %2<br> <br><br> <a href="%3">%3</a>'
|
if (forceUpdate) {
|
||||||
|
if (go.updateCanInstall) {
|
||||||
|
return qsTr('You need to update this app to continue using it.<br>
|
||||||
|
Update now or manually download the most recent version here:<br>
|
||||||
|
<a href="%1">%1</a><br>
|
||||||
|
<a href="https://protonmail.com/support/knowledge-base/update-required/">Learn why</a> you need to update',
|
||||||
|
"Message for force-update").arg(go.updateLandingPage)
|
||||||
|
} else {
|
||||||
|
return qsTr('You need to update this app to continue using it.<br>
|
||||||
|
Download the most recent version here:<br>
|
||||||
|
<a href="%1">%1</a><br>
|
||||||
|
<a href="https://protonmail.com/support/knowledge-base/update-required/">Learn why</a> you need to update',
|
||||||
|
"Message for force-update").arg(go.updateLandingPage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (go.updateCanInstall) {
|
||||||
|
return qsTr('Update to the newest version or download it from:<br>
|
||||||
|
<a href="%1">%1</a><br>
|
||||||
|
<a href="%2">View release notes</a>',
|
||||||
|
"Message for manual update").arg(go.updateLandingPage).arg(go.updateReleaseNotesLink)
|
||||||
|
} else {
|
||||||
|
return qsTr('Update to the newest version from:<br>
|
||||||
|
<a href="%1">%1</a><br>
|
||||||
|
<a href="%2">View release notes</a>',
|
||||||
|
"Message for manual update").arg(go.updateLandingPage).arg(go.updateReleaseNotesLink)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
onLinkActivated : {
|
onLinkActivated : {
|
||||||
console.log("clicked link:", link)
|
console.log("clicked link:", link)
|
||||||
if (link == "releaseNotes"){
|
Qt.openUrlExternally(link)
|
||||||
root.hide()
|
|
||||||
winMain.dialogVersionInfo.show()
|
|
||||||
} else {
|
|
||||||
root.hide()
|
|
||||||
Qt.openUrlExternally(link)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
@ -78,21 +101,30 @@ Dialog {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CheckBoxLabel {
|
||||||
|
id: autoUpdate
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
text: qsTr("Automatically update in the future", "Checkbox label for using autoupdates later on")
|
||||||
|
checked: go.isAutoUpdate
|
||||||
|
onToggled: go.toggleAutoUpdate()
|
||||||
|
visible: !root.forceUpdate
|
||||||
|
}
|
||||||
|
|
||||||
Row {
|
Row {
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
spacing: Style.dialog.spacing
|
spacing: Style.dialog.spacing
|
||||||
|
|
||||||
ButtonRounded {
|
ButtonRounded {
|
||||||
fa_icon: Style.fa.times
|
fa_icon: Style.fa.times
|
||||||
text: (go.goos=="linux" ? qsTr("Okay") : qsTr("Cancel"))
|
text: root.forceUpdate ? qsTr("Quit") : qsTr("Cancel")
|
||||||
color_main: Style.dialog.text
|
color_main: Style.dialog.text
|
||||||
onClicked: root.cancel()
|
onClicked: root.forceUpdate ? Qt.quit() : root.cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
ButtonRounded {
|
ButtonRounded {
|
||||||
fa_icon: Style.fa.check
|
fa_icon: Style.fa.check
|
||||||
text: qsTr("Update")
|
text: qsTr("Update")
|
||||||
visible: go.goos!="linux"
|
visible: go.updateCanInstall
|
||||||
color_main: Style.dialog.text
|
color_main: Style.dialog.text
|
||||||
color_minor: Style.main.textBlue
|
color_minor: Style.main.textBlue
|
||||||
isOpaque: true
|
isOpaque: true
|
||||||
@ -102,7 +134,7 @@ Dialog {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle { // 0: Check / download / unpack / prepare
|
Rectangle { // 1: Installing update
|
||||||
id: updateStatus
|
id: updateStatus
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: parent.height
|
height: parent.height
|
||||||
@ -121,35 +153,29 @@ Dialog {
|
|||||||
width: 2*root.width/3
|
width: 2*root.width/3
|
||||||
horizontalAlignment: Text.AlignHCenter
|
horizontalAlignment: Text.AlignHCenter
|
||||||
wrapMode: Text.Wrap
|
wrapMode: Text.Wrap
|
||||||
text: {
|
text: qsTr("Updating...")
|
||||||
switch (go.progressDescription) {
|
|
||||||
case "1": return qsTr("Checking the current version.")
|
|
||||||
case "2": return qsTr("Downloading the update files.")
|
|
||||||
case "3": return qsTr("Verifying the update files.")
|
|
||||||
case "4": return qsTr("Unpacking the update files.")
|
|
||||||
case "5": return qsTr("Starting the update.")
|
|
||||||
case "6": return qsTr("Quitting the application.")
|
|
||||||
default: return ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ProgressBar {
|
ProgressBar {
|
||||||
id: progressbar
|
id: updateProgressBar
|
||||||
implicitWidth : 2*updateStatus.width/3
|
width: 2*updateStatus.width/3
|
||||||
implicitHeight : Style.exporting.rowHeight
|
height: Style.exporting.rowHeight
|
||||||
visible: go.progress!=0 // hack hide animation when clearing out progress bar
|
//implicitWidth : 2*updateStatus.width/3
|
||||||
value: go.progress
|
//implicitHeight : Style.exporting.rowHeight
|
||||||
property int current: go.total * go.progress
|
indeterminate: true
|
||||||
property bool isFinished: finishedPartBar.width == progressbar.width
|
//value: 0.5
|
||||||
|
//property int current: go.total * go.progress
|
||||||
|
//property bool isFinished: finishedPartBar.width == progressbar.width
|
||||||
background: Rectangle {
|
background: Rectangle {
|
||||||
radius : Style.exporting.boxRadius
|
radius : Style.exporting.boxRadius
|
||||||
color : Style.exporting.progressBackground
|
color : Style.exporting.progressBackground
|
||||||
}
|
}
|
||||||
|
|
||||||
contentItem: Item {
|
contentItem: Item {
|
||||||
|
clip: true
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: finishedPartBar
|
id: progressIndicator
|
||||||
width : parent.width * progressbar.visualPosition
|
width : updateProgressBar.indeterminate ? 50 : parent.width * updateProgressBar.visualPosition
|
||||||
height : parent.height
|
height : parent.height
|
||||||
radius : Style.exporting.boxRadius
|
radius : Style.exporting.boxRadius
|
||||||
gradient : Gradient {
|
gradient : Gradient {
|
||||||
@ -161,6 +187,27 @@ Dialog {
|
|||||||
Behavior on width {
|
Behavior on width {
|
||||||
NumberAnimation { duration:300; easing.type: Easing.InOutQuad }
|
NumberAnimation { duration:300; easing.type: Easing.InOutQuad }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SequentialAnimation {
|
||||||
|
running: updateProgressBar.visible && updateProgressBar.indeterminate
|
||||||
|
loops: Animation.Infinite
|
||||||
|
|
||||||
|
SmoothedAnimation {
|
||||||
|
target: progressIndicator
|
||||||
|
property: "x"
|
||||||
|
from: 0
|
||||||
|
to: updateProgressBar.width - progressIndicator.width
|
||||||
|
duration: 2000
|
||||||
|
}
|
||||||
|
|
||||||
|
SmoothedAnimation {
|
||||||
|
target: progressIndicator
|
||||||
|
property: "x"
|
||||||
|
from: updateProgressBar.width - progressIndicator.width
|
||||||
|
to: 0
|
||||||
|
duration: 2000
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Text {
|
Text {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
@ -175,7 +222,7 @@ Dialog {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle { // 1: Something went wrong / All ok, closing bridge
|
Rectangle { // 2: Something went wrong / All ok, closing bridge
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: parent.height
|
height: parent.height
|
||||||
color: Style.transparent
|
color: Style.transparent
|
||||||
@ -193,8 +240,8 @@ Dialog {
|
|||||||
width: 2*root.width/3
|
width: 2*root.width/3
|
||||||
horizontalAlignment: Text.AlignHCenter
|
horizontalAlignment: Text.AlignHCenter
|
||||||
wrapMode: Text.Wrap
|
wrapMode: Text.Wrap
|
||||||
text: !root.hasError ? qsTr('Application will quit now to finish the update.', "message after successful update") :
|
text: !root.hasError ? qsTr('%1 will restart now to finish the update.', "message after successful update").arg(go.programTitle) :
|
||||||
qsTr('<b>The update procedure was not successful!</b><br>Please follow the download link and update manually. <br><br><a href="%1">%1</a>').arg(go.downloadLink)
|
qsTr('<b>The update procedure was not successful!</b><br>Please follow the download link and update manually. <br><br><a href="%1">%1</a>').arg(go.updateLandingPage)
|
||||||
|
|
||||||
onLinkActivated : {
|
onLinkActivated : {
|
||||||
console.log("clicked link:", link)
|
console.log("clicked link:", link)
|
||||||
@ -225,7 +272,7 @@ Dialog {
|
|||||||
|
|
||||||
function finished(hasError) {
|
function finished(hasError) {
|
||||||
root.hasError = hasError
|
root.hasError = hasError
|
||||||
root.incrementCurrentIndex()
|
root.currentIndex = 2
|
||||||
}
|
}
|
||||||
|
|
||||||
onShow: {
|
onShow: {
|
||||||
@ -239,9 +286,10 @@ Dialog {
|
|||||||
onOkay: {
|
onOkay: {
|
||||||
switch (root.currentIndex) {
|
switch (root.currentIndex) {
|
||||||
case 0:
|
case 0:
|
||||||
go.startUpdate()
|
go.startManualUpdate()
|
||||||
|
root.currentIndex = 1
|
||||||
|
break
|
||||||
}
|
}
|
||||||
root.incrementCurrentIndex()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onCancel: {
|
onCancel: {
|
||||||
|
|||||||
@ -53,6 +53,7 @@ Rectangle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Row {
|
Row {
|
||||||
|
id: messageRow
|
||||||
anchors.centerIn: root
|
anchors.centerIn: root
|
||||||
visible: root.isVisible
|
visible: root.isVisible
|
||||||
spacing: Style.main.leftMarginButton
|
spacing: Style.main.leftMarginButton
|
||||||
@ -63,80 +64,74 @@ Rectangle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ClickIconText {
|
ClickIconText {
|
||||||
|
id: linkText
|
||||||
anchors.verticalCenter : message.verticalCenter
|
anchors.verticalCenter : message.verticalCenter
|
||||||
text : "("+go.newversion+" " + qsTr("release notes", "display the release notes from the new version")+")"
|
|
||||||
visible : root.state=="oldVersion" && ( go.changelog!="" || go.bugfixes!="")
|
|
||||||
iconText : ""
|
iconText : ""
|
||||||
onClicked : {
|
|
||||||
dialogVersionInfo.show()
|
|
||||||
}
|
|
||||||
fontSize : root.fontSize
|
fontSize : root.fontSize
|
||||||
}
|
}
|
||||||
|
|
||||||
ClickIconText {
|
ClickIconText {
|
||||||
|
id: actionText
|
||||||
anchors.verticalCenter : message.verticalCenter
|
anchors.verticalCenter : message.verticalCenter
|
||||||
text : root.state=="oldVersion" || root.state == "forceUpdate" ?
|
|
||||||
qsTr("Update", "click to update to a new version when one is available") :
|
|
||||||
qsTr("Retry now", "click to try to connect to the internet when the app is disconnected from the internet")
|
|
||||||
visible : root.state!="internetCheck"
|
|
||||||
iconText : ""
|
iconText : ""
|
||||||
onClicked : {
|
|
||||||
if (root.state=="oldVersion" || root.state=="forceUpdate" ) {
|
|
||||||
winMain.dialogUpdate.show()
|
|
||||||
} else {
|
|
||||||
go.checkInternet()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fontSize : root.fontSize
|
fontSize : root.fontSize
|
||||||
textUnderline: true
|
textUnderline: true
|
||||||
}
|
}
|
||||||
Text {
|
Text {
|
||||||
|
id: separatorText
|
||||||
anchors.baseline : message.baseline
|
anchors.baseline : message.baseline
|
||||||
color: Style.main.text
|
color: Style.main.text
|
||||||
font {
|
font {
|
||||||
pointSize : root.fontSize * Style.pt
|
pointSize : root.fontSize * Style.pt
|
||||||
bold : true
|
bold : true
|
||||||
}
|
}
|
||||||
visible: root.state=="oldVersion" || root.state=="noInternet"
|
|
||||||
text : "|"
|
|
||||||
}
|
}
|
||||||
ClickIconText {
|
ClickIconText {
|
||||||
|
id: action2Text
|
||||||
anchors.verticalCenter : message.verticalCenter
|
anchors.verticalCenter : message.verticalCenter
|
||||||
iconText : ""
|
iconText : ""
|
||||||
text : root.state == "noInternet" ?
|
|
||||||
qsTr("Troubleshoot", "Show modal screen with additional tips for troubleshooting connection issues") :
|
|
||||||
qsTr("Remind me later", "Do not install new version and dismiss a notification")
|
|
||||||
visible : root.state=="oldVersion" || root.state=="noInternet"
|
|
||||||
onClicked : {
|
|
||||||
if (root.state == "oldVersion") {
|
|
||||||
root.state = "upToDate"
|
|
||||||
}
|
|
||||||
if (root.state == "noInternet") {
|
|
||||||
dialogConnectionTroubleshoot.show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fontSize : root.fontSize
|
fontSize : root.fontSize
|
||||||
textUnderline: true
|
textUnderline: true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ClickIconText {
|
||||||
|
id: closeSign
|
||||||
|
anchors.verticalCenter : messageRow.verticalCenter
|
||||||
|
anchors.right: root.right
|
||||||
|
iconText : Style.fa.close
|
||||||
|
fontSize : root.fontSize
|
||||||
|
textUnderline: true
|
||||||
|
}
|
||||||
|
|
||||||
onStateChanged : {
|
onStateChanged : {
|
||||||
switch (root.state) {
|
switch (root.state) {
|
||||||
case "forceUpdate" :
|
case "internetCheck":
|
||||||
gui.warningFlags |= Style.errorInfoBar
|
|
||||||
break;
|
|
||||||
case "upToDate" :
|
|
||||||
gui.warningFlags &= ~Style.warnInfoBar
|
|
||||||
iTry = 0
|
|
||||||
secLeft=checkInterval[iTry]
|
|
||||||
break;
|
break;
|
||||||
case "noInternet" :
|
case "noInternet" :
|
||||||
gui.warningFlags |= Style.warnInfoBar
|
gui.warningFlags |= Style.warnInfoBar
|
||||||
retryInternet.start()
|
retryInternet.start()
|
||||||
secLeft=checkInterval[iTry]
|
secLeft=checkInterval[iTry]
|
||||||
break;
|
break;
|
||||||
default :
|
case "oldVersion":
|
||||||
gui.warningFlags |= Style.warnInfoBar
|
gui.warningFlags |= Style.warnInfoBar
|
||||||
|
break;
|
||||||
|
case "forceUpdate":
|
||||||
|
gui.warningFlags |= Style.errorInfoBar
|
||||||
|
break;
|
||||||
|
case "upToDate":
|
||||||
|
gui.warningFlags &= ~Style.warnInfoBar
|
||||||
|
iTry = 0
|
||||||
|
secLeft=checkInterval[iTry]
|
||||||
|
break;
|
||||||
|
case "updateRestart":
|
||||||
|
gui.warningFlags |= Style.warnInfoBar
|
||||||
|
break;
|
||||||
|
case "updateError":
|
||||||
|
gui.warningFlags |= Style.errorInfoBar
|
||||||
|
break;
|
||||||
|
default :
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (root.state!="noInternet") {
|
if (root.state!="noInternet") {
|
||||||
@ -172,6 +167,26 @@ Rectangle {
|
|||||||
color: Style.main.background
|
color: Style.main.background
|
||||||
text: qsTr("Checking connection. Please wait...", "displayed after user retries internet connection")
|
text: qsTr("Checking connection. Please wait...", "displayed after user retries internet connection")
|
||||||
}
|
}
|
||||||
|
PropertyChanges {
|
||||||
|
target: linkText
|
||||||
|
visible: false
|
||||||
|
}
|
||||||
|
PropertyChanges {
|
||||||
|
target: actionText
|
||||||
|
visible: false
|
||||||
|
}
|
||||||
|
PropertyChanges {
|
||||||
|
target: separatorText
|
||||||
|
visible: false
|
||||||
|
}
|
||||||
|
PropertyChanges {
|
||||||
|
target: action2Text
|
||||||
|
visible: false
|
||||||
|
}
|
||||||
|
PropertyChanges {
|
||||||
|
target: closeSign
|
||||||
|
visible: false
|
||||||
|
}
|
||||||
},
|
},
|
||||||
State {
|
State {
|
||||||
name: "noInternet"
|
name: "noInternet"
|
||||||
@ -186,6 +201,35 @@ Rectangle {
|
|||||||
color: Style.main.line
|
color: Style.main.line
|
||||||
text: qsTr("Cannot contact server. Retrying in ", "displayed when the app is disconnected from the internet or server has problems")+timeToRetry()+"."
|
text: qsTr("Cannot contact server. Retrying in ", "displayed when the app is disconnected from the internet or server has problems")+timeToRetry()+"."
|
||||||
}
|
}
|
||||||
|
PropertyChanges {
|
||||||
|
target: linkText
|
||||||
|
visible: false
|
||||||
|
}
|
||||||
|
PropertyChanges {
|
||||||
|
target: actionText
|
||||||
|
visible: true
|
||||||
|
text: qsTr("Retry now", "click to try to connect to the internet when the app is disconnected from the internet")
|
||||||
|
onClicked: {
|
||||||
|
go.checkInternet()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PropertyChanges {
|
||||||
|
target: separatorText
|
||||||
|
visible: true
|
||||||
|
text: "|"
|
||||||
|
}
|
||||||
|
PropertyChanges {
|
||||||
|
target: action2Text
|
||||||
|
visible: true
|
||||||
|
text: qsTr("Troubleshoot", "Show modal screen with additional tips for troubleshooting connection issues")
|
||||||
|
onClicked: {
|
||||||
|
dialogConnectionTroubleshoot.show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PropertyChanges {
|
||||||
|
target: closeSign
|
||||||
|
visible: false
|
||||||
|
}
|
||||||
},
|
},
|
||||||
State {
|
State {
|
||||||
name: "oldVersion"
|
name: "oldVersion"
|
||||||
@ -198,7 +242,36 @@ Rectangle {
|
|||||||
PropertyChanges {
|
PropertyChanges {
|
||||||
target: message
|
target: message
|
||||||
color: Style.main.background
|
color: Style.main.background
|
||||||
text: qsTr("An update is available.", "displayed in a notification when an app update is available")
|
text: qsTr("Update available", "displayed in a notification when an app update is available")
|
||||||
|
}
|
||||||
|
PropertyChanges {
|
||||||
|
target: linkText
|
||||||
|
visible: true
|
||||||
|
text: "(" + qsTr("view release notes", "display the release notes from the new version") + ")"
|
||||||
|
onClicked: gui.openReleaseNotes()
|
||||||
|
}
|
||||||
|
PropertyChanges {
|
||||||
|
target: actionText
|
||||||
|
visible: true
|
||||||
|
text: qsTr("Update", "click to update to a new version when one is available")
|
||||||
|
onClicked: {
|
||||||
|
winMain.dialogUpdate.show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PropertyChanges {
|
||||||
|
target: separatorText
|
||||||
|
visible: false
|
||||||
|
}
|
||||||
|
PropertyChanges {
|
||||||
|
target: action2Text
|
||||||
|
visible: false
|
||||||
|
}
|
||||||
|
PropertyChanges {
|
||||||
|
target: closeSign
|
||||||
|
visible: true
|
||||||
|
onClicked: {
|
||||||
|
root.state = "upToDate"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
State {
|
State {
|
||||||
@ -214,6 +287,30 @@ Rectangle {
|
|||||||
color: Style.main.line
|
color: Style.main.line
|
||||||
text: qsTr("%1 is outdated.", "displayed in a notification when app is outdated").arg(go.programTitle)
|
text: qsTr("%1 is outdated.", "displayed in a notification when app is outdated").arg(go.programTitle)
|
||||||
}
|
}
|
||||||
|
PropertyChanges {
|
||||||
|
target: linkText
|
||||||
|
visible: false
|
||||||
|
}
|
||||||
|
PropertyChanges {
|
||||||
|
target: actionText
|
||||||
|
visible: true
|
||||||
|
text: qsTr("Update", "click to update to a new version when one is available")
|
||||||
|
onClicked: {
|
||||||
|
winMain.dialogUpdate.show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PropertyChanges {
|
||||||
|
target: separatorText
|
||||||
|
visible: false
|
||||||
|
}
|
||||||
|
PropertyChanges {
|
||||||
|
target: action2Text
|
||||||
|
visible: false
|
||||||
|
}
|
||||||
|
PropertyChanges {
|
||||||
|
target: closeSign
|
||||||
|
visible: false
|
||||||
|
}
|
||||||
},
|
},
|
||||||
State {
|
State {
|
||||||
name: "upToDate"
|
name: "upToDate"
|
||||||
@ -228,6 +325,103 @@ Rectangle {
|
|||||||
color: Style.main.background
|
color: Style.main.background
|
||||||
text: ""
|
text: ""
|
||||||
}
|
}
|
||||||
|
PropertyChanges {
|
||||||
|
target: linkText
|
||||||
|
visible: false
|
||||||
|
}
|
||||||
|
PropertyChanges {
|
||||||
|
target: actionText
|
||||||
|
visible: false
|
||||||
|
}
|
||||||
|
PropertyChanges {
|
||||||
|
target: separatorText
|
||||||
|
visible: false
|
||||||
|
}
|
||||||
|
PropertyChanges {
|
||||||
|
target: action2Text
|
||||||
|
visible: false
|
||||||
|
}
|
||||||
|
PropertyChanges {
|
||||||
|
target: closeSign
|
||||||
|
visible: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
State {
|
||||||
|
name: "updateRestart"
|
||||||
|
PropertyChanges {
|
||||||
|
target: root
|
||||||
|
height: 2* Style.main.fontSize
|
||||||
|
isVisible: true
|
||||||
|
color: Style.main.textBlue
|
||||||
|
}
|
||||||
|
PropertyChanges {
|
||||||
|
target: message
|
||||||
|
color: Style.main.background
|
||||||
|
text: qsTr("%1 update is ready", "displayed in a notification when an app update is installed and restart is needed").arg(go.programTitle)
|
||||||
|
}
|
||||||
|
PropertyChanges {
|
||||||
|
target: linkText
|
||||||
|
visible: false
|
||||||
|
}
|
||||||
|
PropertyChanges {
|
||||||
|
target: actionText
|
||||||
|
visible: true
|
||||||
|
text: qsTr("Restart now", "click to restart application as new version was installed")
|
||||||
|
onClicked: {
|
||||||
|
go.setToRestart()
|
||||||
|
Qt.quit()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PropertyChanges {
|
||||||
|
target: separatorText
|
||||||
|
visible: false
|
||||||
|
}
|
||||||
|
PropertyChanges {
|
||||||
|
target: action2Text
|
||||||
|
visible: false
|
||||||
|
}
|
||||||
|
PropertyChanges {
|
||||||
|
target: closeSign
|
||||||
|
visible: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
State {
|
||||||
|
name: "updateError"
|
||||||
|
PropertyChanges {
|
||||||
|
target: root
|
||||||
|
height: 2* Style.main.fontSize
|
||||||
|
isVisible: true
|
||||||
|
color: Style.main.textRed
|
||||||
|
}
|
||||||
|
PropertyChanges {
|
||||||
|
target: message
|
||||||
|
color: Style.main.line
|
||||||
|
text: qsTr("Sorry, %1 couldn't update.", "displayed in a notification when app failed to autoupdate").arg(go.programTitle)
|
||||||
|
}
|
||||||
|
PropertyChanges {
|
||||||
|
target: linkText
|
||||||
|
visible: false
|
||||||
|
}
|
||||||
|
PropertyChanges {
|
||||||
|
target: actionText
|
||||||
|
visible: true
|
||||||
|
text: qsTr("Please update manually", "click to open download page to update manally")
|
||||||
|
onClicked: {
|
||||||
|
Qt.openUrlExternally(go.updateLandingPage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PropertyChanges {
|
||||||
|
target: separatorText
|
||||||
|
visible: false
|
||||||
|
}
|
||||||
|
PropertyChanges {
|
||||||
|
target: action2Text
|
||||||
|
visible: false
|
||||||
|
}
|
||||||
|
PropertyChanges {
|
||||||
|
target: closeSign
|
||||||
|
visible: false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -59,7 +59,6 @@ QtObject {
|
|||||||
property real fontSize : 12 * px
|
property real fontSize : 12 * px
|
||||||
property real iconSize : 15 * px
|
property real iconSize : 15 * px
|
||||||
property real leftMarginButton : 9 * px
|
property real leftMarginButton : 9 * px
|
||||||
property real verCheckRepeatTime : 15*60*60*1000 // milliseconds
|
|
||||||
property real topMargin : fontSize
|
property real topMargin : fontSize
|
||||||
property real bottomMargin : fontSize
|
property real bottomMargin : fontSize
|
||||||
property real border : 1 * px
|
property real border : 1 * px
|
||||||
|
|||||||
@ -108,9 +108,14 @@ Window {
|
|||||||
ListElement { title: "Logout bridge" }
|
ListElement { title: "Logout bridge" }
|
||||||
ListElement { title: "Internet on" }
|
ListElement { title: "Internet on" }
|
||||||
ListElement { title: "Internet off" }
|
ListElement { title: "Internet off" }
|
||||||
ListElement { title: "NeedUpdate" }
|
|
||||||
ListElement { title: "UpToDate" }
|
ListElement { title: "UpToDate" }
|
||||||
ListElement { title: "ForceUpdate" }
|
ListElement { title: "NotifyManualUpdate(CanInstall)" }
|
||||||
|
ListElement { title: "NotifyManualUpdate(CantInstall)" }
|
||||||
|
ListElement { title: "NotifyManualUpdateRestart" }
|
||||||
|
ListElement { title: "NotifyManualUpdateError" }
|
||||||
|
ListElement { title: "ForceUpdate" }
|
||||||
|
ListElement { title: "NotifySilentUpdateRestartNeeded" }
|
||||||
|
ListElement { title: "NotifySilentUpdateError" }
|
||||||
ListElement { title: "Linux" }
|
ListElement { title: "Linux" }
|
||||||
ListElement { title: "Windows" }
|
ListElement { title: "Windows" }
|
||||||
ListElement { title: "Macos" }
|
ListElement { title: "Macos" }
|
||||||
@ -196,11 +201,28 @@ Window {
|
|||||||
case "UpToDate" :
|
case "UpToDate" :
|
||||||
testroot.newVersion = false
|
testroot.newVersion = false
|
||||||
break;
|
break;
|
||||||
case "NeedUpdate" :
|
case "NotifyManualUpdate(CanInstall)" :
|
||||||
testroot.newVersion = true
|
go.notifyManualUpdate()
|
||||||
|
go.updateCanInstall = true
|
||||||
|
break;
|
||||||
|
case "NotifyManualUpdate(CantInstall)" :
|
||||||
|
go.notifyManualUpdate()
|
||||||
|
go.updateCanInstall = false
|
||||||
|
break;
|
||||||
|
case "NotifyManualUpdateRestart":
|
||||||
|
go.notifyManualUpdateRestartNeeded()
|
||||||
|
break;
|
||||||
|
case "NotifyManualUpdateError":
|
||||||
|
go.notifyManualUpdateError()
|
||||||
break;
|
break;
|
||||||
case "ForceUpdate" :
|
case "ForceUpdate" :
|
||||||
go.notifyUpdate()
|
go.notifyForceUpdate()
|
||||||
|
break;
|
||||||
|
case "NotifySilentUpdateRestartNeeded" :
|
||||||
|
go.notifySilentUpdateRestartNeeded()
|
||||||
|
break;
|
||||||
|
case "NotifySilentUpdateError" :
|
||||||
|
go.notifySilentUpdateError()
|
||||||
break;
|
break;
|
||||||
case "SendAlertPopup" :
|
case "SendAlertPopup" :
|
||||||
go.showOutgoingNoEncPopup("Alert sending unencrypted!")
|
go.showOutgoingNoEncPopup("Alert sending unencrypted!")
|
||||||
@ -244,6 +266,8 @@ Window {
|
|||||||
id: go
|
id: go
|
||||||
|
|
||||||
property bool isAutoStart : true
|
property bool isAutoStart : true
|
||||||
|
property bool isAutoUpdate : false
|
||||||
|
property bool isEarlyAccess : false
|
||||||
property bool isProxyAllowed : false
|
property bool isProxyAllowed : false
|
||||||
property bool isFirstStart : false
|
property bool isFirstStart : false
|
||||||
property bool isFreshVersion : false
|
property bool isFreshVersion : false
|
||||||
@ -269,19 +293,41 @@ Window {
|
|||||||
property string genericErrSeeLogs
|
property string genericErrSeeLogs
|
||||||
|
|
||||||
property string programTitle : "ProtonMail Bridge"
|
property string programTitle : "ProtonMail Bridge"
|
||||||
property string newversion : "QA.1.0"
|
|
||||||
property string fullversion : "QA.1.0 (d9f8sdf9) 2020-02-19T10:57:23+01:00"
|
property string fullversion : "QA.1.0 (d9f8sdf9) 2020-02-19T10:57:23+01:00"
|
||||||
property string landingPage : "https://landing.page"
|
|
||||||
//property string downloadLink: "https://landing.page/download/link"
|
|
||||||
property string downloadLink: "https://protonmail.com/download/beta/protonmail-bridge-1.1.5-1.x86_64.rpm;https://www.protonmail.com/downloads/beta/Desktop-Bridge-link1.exe;https://www.protonmail.com/downloads/beta/Desktop-Bridge-link1.exe;https://www.protonmail.com/downloads/beta/Desktop-Bridge-link1.exe;"
|
property string downloadLink: "https://protonmail.com/download/beta/protonmail-bridge-1.1.5-1.x86_64.rpm;https://www.protonmail.com/downloads/beta/Desktop-Bridge-link1.exe;https://www.protonmail.com/downloads/beta/Desktop-Bridge-link1.exe;https://www.protonmail.com/downloads/beta/Desktop-Bridge-link1.exe;"
|
||||||
//property string changelog : "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet."
|
|
||||||
property string changelog : "• Support of encryption to external PGP recipients using contacts created on beta.protonmail.com (see https://protonmail.com/blog/pgp-vulnerability-efail/ to understand the vulnerabilities that may be associated with sending to other PGP clients)\n• Notification that outgoing email will be delivered as non-encrypted.\n• NOTE: Due to a change of the keychain format, you will need to add your account(s) to the Bridge after installing this version"
|
property string updateVersion : "QA.1.0"
|
||||||
property string bugfixes : "• Support accounts with same user names\n• Support sending vCalendar event"
|
property bool updateCanInstall: true
|
||||||
|
property string updateLandingPage : "https://protonmail.com/bridge/download/"
|
||||||
|
property string updateReleaseNotesLink : "" // "https://protonmail.com/download/bridge/release_notes.html"
|
||||||
|
signal notifyManualUpdate()
|
||||||
|
signal notifyManualUpdateRestartNeeded()
|
||||||
|
signal notifyManualUpdateError()
|
||||||
|
signal notifyForceUpdate()
|
||||||
|
signal notifySilentUpdateRestartNeeded()
|
||||||
|
signal notifySilentUpdateError()
|
||||||
|
function checkForUpdates() {
|
||||||
|
console.log("checkForUpdates")
|
||||||
|
go.notifyVersionIsTheLatest()
|
||||||
|
}
|
||||||
|
function startManualUpdate() {
|
||||||
|
console.log("startManualUpdate")
|
||||||
|
}
|
||||||
|
function checkAndOpenReleaseNotes() {
|
||||||
|
console.log("check for release notes")
|
||||||
|
go.updateReleaseNotesLink = "https://protonmail.com/download/bridge/release_notes.html"
|
||||||
|
go.openReleaseNotesExternally()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
property string credits : "here;goes;list;;of;;used;packages;"
|
property string credits : "here;goes;list;;of;;used;packages;"
|
||||||
|
|
||||||
property real progress: 0.3
|
property real progress: 0.3
|
||||||
property int progressDescription: 2
|
property int progressDescription: 2
|
||||||
|
|
||||||
|
function setToRestart() {
|
||||||
|
console.log("setting to restart")
|
||||||
|
}
|
||||||
|
|
||||||
signal toggleMainWin(int systX, int systY, int systW, int systH)
|
signal toggleMainWin(int systX, int systY, int systW, int systH)
|
||||||
|
|
||||||
@ -297,22 +343,24 @@ Window {
|
|||||||
|
|
||||||
signal processFinished()
|
signal processFinished()
|
||||||
signal toggleAutoStart()
|
signal toggleAutoStart()
|
||||||
|
signal toggleEarlyAccess()
|
||||||
|
signal toggleAutoUpdate()
|
||||||
signal notifyBubble(int tabIndex, string message)
|
signal notifyBubble(int tabIndex, string message)
|
||||||
signal silentBubble(int tabIndex, string message)
|
signal silentBubble(int tabIndex, string message)
|
||||||
signal runCheckVersion(bool showMessage)
|
|
||||||
signal setAddAccountWarning(string message)
|
signal setAddAccountWarning(string message)
|
||||||
|
|
||||||
signal notifyUpdate()
|
|
||||||
signal notifyFirewall()
|
signal notifyFirewall()
|
||||||
signal notifyLogout(string accname)
|
signal notifyLogout(string accname)
|
||||||
signal notifyAddressChanged(string accname)
|
signal notifyAddressChanged(string accname)
|
||||||
signal notifyAddressChangedLogout(string accname)
|
signal notifyAddressChangedLogout(string accname)
|
||||||
signal failedAutostartCode(string code)
|
signal failedAutostartCode(string code)
|
||||||
|
|
||||||
|
signal openReleaseNotesExternally()
|
||||||
signal showCertIssue()
|
signal showCertIssue()
|
||||||
|
|
||||||
signal updateFinished(bool hasError)
|
signal updateFinished(bool hasError)
|
||||||
|
|
||||||
|
signal guiIsReady()
|
||||||
|
|
||||||
signal showOutgoingNoEncPopup(string subject)
|
signal showOutgoingNoEncPopup(string subject)
|
||||||
signal setOutgoingNoEncPopupCoord(real x, real y)
|
signal setOutgoingNoEncPopupCoord(real x, real y)
|
||||||
@ -465,9 +513,6 @@ Window {
|
|||||||
switch (timer.work) {
|
switch (timer.work) {
|
||||||
case "wait":
|
case "wait":
|
||||||
break
|
break
|
||||||
case "startUpdate":
|
|
||||||
go.animateProgressBar.start()
|
|
||||||
go.updateFinished(true)
|
|
||||||
default:
|
default:
|
||||||
go.processFinished()
|
go.processFinished()
|
||||||
}
|
}
|
||||||
@ -478,11 +523,6 @@ Window {
|
|||||||
timer.start()
|
timer.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
function startUpdate() {
|
|
||||||
timer.work="startUpdate"
|
|
||||||
timer.start()
|
|
||||||
}
|
|
||||||
|
|
||||||
function loadAccounts() {
|
function loadAccounts() {
|
||||||
console.log("Test: Account loaded")
|
console.log("Test: Account loaded")
|
||||||
}
|
}
|
||||||
@ -502,21 +542,7 @@ Window {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getLocalVersionInfo(){
|
function getLocalVersionInfo(){
|
||||||
go.newversion = "QA.1.0"
|
go.updateVersion = "QA.1.0"
|
||||||
}
|
|
||||||
|
|
||||||
function isNewVersionAvailable(showMessage){
|
|
||||||
if (testroot.newVersion) {
|
|
||||||
go.newversion = "QA.2.0"
|
|
||||||
setUpdateState("oldVersion")
|
|
||||||
} else {
|
|
||||||
go.newversion = "QA.1.0"
|
|
||||||
setUpdateState("upToDate")
|
|
||||||
if(showMessage) {
|
|
||||||
notifyVersionIsTheLatest()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
workAndClose()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getBackendVersion() {
|
function getBackendVersion() {
|
||||||
@ -566,7 +592,6 @@ Window {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
property bool isRestarting: false
|
|
||||||
function setPortsAndSecurity(portIMAP, portSMTP, secSMTP) {
|
function setPortsAndSecurity(portIMAP, portSMTP, secSMTP) {
|
||||||
console.log("Test: ports changed", portIMAP, portSMTP, secSMTP)
|
console.log("Test: ports changed", portIMAP, portSMTP, secSMTP)
|
||||||
}
|
}
|
||||||
@ -606,6 +631,18 @@ Window {
|
|||||||
isAutoStart = (isAutoStart!=false) ? false : true
|
isAutoStart = (isAutoStart!=false) ? false : true
|
||||||
console.log (" Test: toggleAutoStart "+isAutoStart)
|
console.log (" Test: toggleAutoStart "+isAutoStart)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onToggleAutoUpdate: {
|
||||||
|
workAndClose()
|
||||||
|
isAutoUpdate = (isAutoUpdate!=false) ? false : true
|
||||||
|
console.log (" Test: onToggleAutoUpdate "+isAutoUpdate)
|
||||||
|
}
|
||||||
|
|
||||||
|
onToggleEarlyAccess: {
|
||||||
|
workAndClose()
|
||||||
|
isEarlyAccess = (isEarlyAccess!=false) ? false : true
|
||||||
|
console.log (" Test: onToggleEarlyAccess "+isEarlyAccess)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -98,24 +98,29 @@ Window {
|
|||||||
ListModel {
|
ListModel {
|
||||||
id: buttons
|
id: buttons
|
||||||
|
|
||||||
ListElement { title : "Show window" }
|
ListElement { title : "Show window" }
|
||||||
ListElement { title : "Logout" }
|
ListElement { title : "Logout" }
|
||||||
ListElement { title : "Internet on" }
|
ListElement { title : "Internet on" }
|
||||||
ListElement { title : "Internet off" }
|
ListElement { title : "Internet off" }
|
||||||
ListElement { title : "Macos" }
|
ListElement { title : "Macos" }
|
||||||
ListElement { title : "Windows" }
|
ListElement { title : "Windows" }
|
||||||
ListElement { title : "Linux" }
|
ListElement { title : "Linux" }
|
||||||
ListElement { title : "New Version" }
|
ListElement { title: "NotifyManualUpdate(CanInstall)" }
|
||||||
ListElement { title : "ForceUpgrade" }
|
ListElement { title: "NotifyManualUpdate(CantInstall)" }
|
||||||
ListElement { title : "ImportStructure" }
|
ListElement { title: "NotifyManualUpdateRestart" }
|
||||||
ListElement { title : "DraftImpFailed" }
|
ListElement { title: "NotifyManualUpdateError" }
|
||||||
ListElement { title : "NoInterImp" }
|
ListElement { title: "ForceUpdate" }
|
||||||
ListElement { title : "ReportImp" }
|
ListElement { title: "NotifySilentUpdateRestartNeeded" }
|
||||||
ListElement { title : "NewFolder" }
|
ListElement { title: "NotifySilentUpdateError" }
|
||||||
ListElement { title : "EditFolder" }
|
ListElement { title : "ImportStructure" }
|
||||||
ListElement { title : "EditLabel" }
|
ListElement { title : "DraftImpFailed" }
|
||||||
ListElement { title : "ExpProgErr" }
|
ListElement { title : "NoInterImp" }
|
||||||
ListElement { title : "ImpProgErr" }
|
ListElement { title : "ReportImp" }
|
||||||
|
ListElement { title : "NewFolder" }
|
||||||
|
ListElement { title : "EditFolder" }
|
||||||
|
ListElement { title : "EditLabel" }
|
||||||
|
ListElement { title : "ExpProgErr" }
|
||||||
|
ListElement { title : "ImpProgErr" }
|
||||||
}
|
}
|
||||||
|
|
||||||
ListView {
|
ListView {
|
||||||
@ -161,12 +166,28 @@ Window {
|
|||||||
case "Linux" :
|
case "Linux" :
|
||||||
go.goos = "linux";
|
go.goos = "linux";
|
||||||
break;
|
break;
|
||||||
case "New Version" :
|
case "NotifyManualUpdate(CanInstall)" :
|
||||||
testroot.newVersion = !testroot.newVersion
|
go.notifyManualUpdate()
|
||||||
systrText.text = testroot.newVersion ? "new version" : "uptodate"
|
go.updateCanInstall = true
|
||||||
break
|
break;
|
||||||
case "ForceUpgrade" :
|
case "NotifyManualUpdate(CantInstall)" :
|
||||||
go.notifyUpgrade()
|
go.notifyManualUpdate()
|
||||||
|
go.updateCanInstall = false
|
||||||
|
break;
|
||||||
|
case "NotifyManualUpdateRestart":
|
||||||
|
go.notifyManualUpdateRestartNeeded()
|
||||||
|
break;
|
||||||
|
case "NotifyManualUpdateError":
|
||||||
|
go.notifyManualUpdateError()
|
||||||
|
break;
|
||||||
|
case "ForceUpdate" :
|
||||||
|
go.notifyForceUpdate()
|
||||||
|
break;
|
||||||
|
case "NotifySilentUpdateRestartNeeded" :
|
||||||
|
go.notifySilentUpdateRestartNeeded()
|
||||||
|
break;
|
||||||
|
case "NotifySilentUpdateError" :
|
||||||
|
go.notifySilentUpdateError()
|
||||||
break;
|
break;
|
||||||
case "ImportStructure" :
|
case "ImportStructure" :
|
||||||
testgui.winMain.dialogImport.address = "cuto@pm.com"
|
testgui.winMain.dialogImport.address = "cuto@pm.com"
|
||||||
@ -815,6 +836,7 @@ Window {
|
|||||||
id: go
|
id: go
|
||||||
|
|
||||||
property int isAutoStart : 1
|
property int isAutoStart : 1
|
||||||
|
property bool isAutoUpdate : false
|
||||||
property bool isFirstStart : false
|
property bool isFirstStart : false
|
||||||
property string currentAddress : "none"
|
property string currentAddress : "none"
|
||||||
//property string goos : "windows"
|
//property string goos : "windows"
|
||||||
@ -831,12 +853,31 @@ Window {
|
|||||||
property string bugReportSent
|
property string bugReportSent
|
||||||
|
|
||||||
property string programTitle : "ProtonMail Import-Export app"
|
property string programTitle : "ProtonMail Import-Export app"
|
||||||
property string newversion : "q0.1.0"
|
property string fullversion : "QA.1.0 (d9f8sdf9) 2020-02-19T10:57:23+01:00"
|
||||||
property string landingPage : "https://landing.page"
|
property string downloadLink: "https://protonmail.com/download/beta/protonmail-bridge-1.1.5-1.x86_64.rpm;https://www.protonmail.com/downloads/beta/Desktop-Bridge-link1.exe;https://www.protonmail.com/downloads/beta/Desktop-Bridge-link1.exe;https://www.protonmail.com/downloads/beta/Desktop-Bridge-link1.exe;"
|
||||||
property string changelog : "• Lorem ipsum dolor sit amet\n• consetetur sadipscing elitr,\n• sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat,\n• sed diam voluptua.\n• At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet."
|
|
||||||
//property string changelog : ""
|
property string updateVersion : "q0.1.0"
|
||||||
property string bugfixes : "• lorem ipsum dolor sit amet;• consetetur sadipscing elitr;• sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat;• sed diam voluptua;• at vero eos et accusam et justo duo dolores et ea rebum;• stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet"
|
property bool updateCanInstall: true
|
||||||
//property string bugfixes : ""
|
property string updateLandingPage : "https://protonmail.com/import-export/download/"
|
||||||
|
property string updateReleaseNotesLink : "https://protonmail.com/download/ie/release_notes.html"
|
||||||
|
signal notifyManualUpdate()
|
||||||
|
signal notifyManualUpdateRestartNeeded()
|
||||||
|
signal notifyManualUpdateError()
|
||||||
|
signal notifyForceUpdate()
|
||||||
|
signal notifySilentUpdateRestartNeeded()
|
||||||
|
signal notifySilentUpdateError()
|
||||||
|
function checkForUpdates() {
|
||||||
|
console.log("checkForUpdates")
|
||||||
|
go.notifyVersionIsTheLatest()
|
||||||
|
}
|
||||||
|
function startManualUpdate() {
|
||||||
|
console.log("startManualUpdate")
|
||||||
|
}
|
||||||
|
function checkAndOpenReleaseNotes() {
|
||||||
|
console.log("check for release notes")
|
||||||
|
go.updateReleaseNotesLink = "https://protonmail.com/download/import-export/release_notes.html"
|
||||||
|
go.openReleaseNotesExternally()
|
||||||
|
}
|
||||||
|
|
||||||
property real progress: 0.0
|
property real progress: 0.0
|
||||||
property int progressFails: 0
|
property int progressFails: 0
|
||||||
@ -849,13 +890,10 @@ Window {
|
|||||||
|
|
||||||
signal toggleMainWin(int systX, int systY, int systW, int systH)
|
signal toggleMainWin(int systX, int systY, int systW, int systH)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
signal notifyHasNoKeychain()
|
signal notifyHasNoKeychain()
|
||||||
signal notifyKeychainRebuild()
|
signal notifyKeychainRebuild()
|
||||||
signal notifyAddressChangedLogout()
|
signal notifyAddressChangedLogout()
|
||||||
signal notifyAddressChanged()
|
signal notifyAddressChanged()
|
||||||
signal notifyUpdate()
|
|
||||||
|
|
||||||
signal showWindow()
|
signal showWindow()
|
||||||
signal showHelp()
|
signal showHelp()
|
||||||
@ -874,17 +912,25 @@ Window {
|
|||||||
|
|
||||||
signal processFinished()
|
signal processFinished()
|
||||||
signal toggleAutoStart()
|
signal toggleAutoStart()
|
||||||
|
signal toggleAutoUpdate()
|
||||||
signal notifyBubble(int tabIndex, string message)
|
signal notifyBubble(int tabIndex, string message)
|
||||||
signal runCheckVersion(bool showMessage)
|
|
||||||
signal setAddAccountWarning(string message)
|
signal setAddAccountWarning(string message)
|
||||||
signal notifyUpgrade()
|
signal notifyUpdate()
|
||||||
signal updateFinished(bool hasError)
|
signal updateFinished(bool hasError)
|
||||||
|
|
||||||
|
signal guiIsReady()
|
||||||
|
|
||||||
|
signal openReleaseNotesExternally()
|
||||||
|
|
||||||
signal notifyLogout(string accname)
|
signal notifyLogout(string accname)
|
||||||
|
|
||||||
signal notifyError(int errCode)
|
signal notifyError(int errCode)
|
||||||
property string errorDescription : ""
|
property string errorDescription : ""
|
||||||
|
|
||||||
|
function setToRestart() {
|
||||||
|
console.log("setting to restart")
|
||||||
|
}
|
||||||
|
|
||||||
function delay(duration) {
|
function delay(duration) {
|
||||||
var timeStart = new Date().getTime();
|
var timeStart = new Date().getTime();
|
||||||
|
|
||||||
@ -958,7 +1004,7 @@ Window {
|
|||||||
workAndClose("addAccount")
|
workAndClose("addAccount")
|
||||||
}
|
}
|
||||||
|
|
||||||
property SequentialAnimation animateProgressBarUpgrade : SequentialAnimation {
|
property SequentialAnimation animateProgressBarUpdate : SequentialAnimation {
|
||||||
// version
|
// version
|
||||||
PropertyAnimation{ target: go; properties: "progressDescription"; to: 1; duration: 1; }
|
PropertyAnimation{ target: go; properties: "progressDescription"; to: 1; duration: 1; }
|
||||||
PropertyAnimation{ duration: 2000; }
|
PropertyAnimation{ duration: 2000; }
|
||||||
@ -1069,7 +1115,6 @@ Window {
|
|||||||
onTriggered : {
|
onTriggered : {
|
||||||
console.log("triggered "+timer.work)
|
console.log("triggered "+timer.work)
|
||||||
switch (timer.work) {
|
switch (timer.work) {
|
||||||
case "isNewVersionAvailable" :
|
|
||||||
case "clearCache" :
|
case "clearCache" :
|
||||||
case "clearKeychain" :
|
case "clearKeychain" :
|
||||||
case "logout" :
|
case "logout" :
|
||||||
@ -1096,8 +1141,8 @@ Window {
|
|||||||
go.animateProgressBar.start()
|
go.animateProgressBar.start()
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "startUpgrade":
|
case "startManualUpdate":
|
||||||
go.animateProgressBarUpgrade.start()
|
go.animateProgressBarUpdate.start()
|
||||||
go.updateFinished(true)
|
go.updateFinished(true)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@ -1108,18 +1153,10 @@ Window {
|
|||||||
|
|
||||||
function workAndClose(workDescription) {
|
function workAndClose(workDescription) {
|
||||||
go.progress=0.0
|
go.progress=0.0
|
||||||
timer.work = workDescription
|
timer.work = workDescription === undefined ? "" : workDescription
|
||||||
timer.start()
|
timer.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
function startUpgrade() {
|
|
||||||
timer.work="startUpgrade"
|
|
||||||
timer.start()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function checkPathStatus(path) {
|
function checkPathStatus(path) {
|
||||||
if ( path == "" ) return testgui.enums.pathEmptyPath
|
if ( path == "" ) return testgui.enums.pathEmptyPath
|
||||||
if ( path == "wrong" ) return testgui.enums.pathWrongPath
|
if ( path == "wrong" ) return testgui.enums.pathWrongPath
|
||||||
@ -1221,20 +1258,6 @@ Window {
|
|||||||
workAndClose("switchAddressMode")
|
workAndClose("switchAddressMode")
|
||||||
}
|
}
|
||||||
|
|
||||||
function isNewVersionAvailable(showMessage){
|
|
||||||
if (testroot.newVersion) {
|
|
||||||
setUpdateState("oldVersion")
|
|
||||||
} else {
|
|
||||||
setUpdateState("upToDate")
|
|
||||||
if(showMessage) {
|
|
||||||
notifyVersionIsTheLatest()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
workAndClose("isNewVersionAvailable")
|
|
||||||
//notifyBubble(2,go.versionCheckFailed)
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
function getLocalVersionInfo(){}
|
function getLocalVersionInfo(){}
|
||||||
|
|
||||||
function getBackendVersion() {
|
function getBackendVersion() {
|
||||||
@ -1331,5 +1354,11 @@ Window {
|
|||||||
console.log("sending import report from ", address, " file ", fname)
|
console.log("sending import report from ", address, " file ", fname)
|
||||||
return !fname.includes("fail")
|
return !fname.includes("fail")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onToggleAutoUpdate: {
|
||||||
|
workAndClose()
|
||||||
|
isAutoUpdate = (isAutoUpdate!=false) ? false : true
|
||||||
|
console.log (" Test: onToggleAutoUpdate "+isAutoUpdate)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -25,10 +25,9 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/ProtonMail/proton-bridge/internal/bridge"
|
"github.com/ProtonMail/proton-bridge/internal/bridge"
|
||||||
|
"github.com/ProtonMail/proton-bridge/internal/config/settings"
|
||||||
"github.com/ProtonMail/proton-bridge/internal/events"
|
"github.com/ProtonMail/proton-bridge/internal/events"
|
||||||
"github.com/ProtonMail/proton-bridge/internal/frontend/types"
|
"github.com/ProtonMail/proton-bridge/internal/frontend/types"
|
||||||
"github.com/ProtonMail/proton-bridge/internal/preferences"
|
|
||||||
"github.com/ProtonMail/proton-bridge/pkg/config"
|
|
||||||
"github.com/ProtonMail/proton-bridge/pkg/keychain"
|
"github.com/ProtonMail/proton-bridge/pkg/keychain"
|
||||||
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
||||||
)
|
)
|
||||||
@ -38,7 +37,6 @@ type QMLer interface {
|
|||||||
ProcessFinished()
|
ProcessFinished()
|
||||||
NotifyHasNoKeychain()
|
NotifyHasNoKeychain()
|
||||||
SetConnectionStatus(bool)
|
SetConnectionStatus(bool)
|
||||||
SetIsRestarting(bool)
|
|
||||||
SetAddAccountWarning(string, int)
|
SetAddAccountWarning(string, int)
|
||||||
NotifyBubble(int, string)
|
NotifyBubble(int, string)
|
||||||
EmitEvent(string, string)
|
EmitEvent(string, string)
|
||||||
@ -50,23 +48,25 @@ type QMLer interface {
|
|||||||
|
|
||||||
// Accounts holds functionality of users
|
// Accounts holds functionality of users
|
||||||
type Accounts struct {
|
type Accounts struct {
|
||||||
Model *AccountsModel
|
Model *AccountsModel
|
||||||
qml QMLer
|
qml QMLer
|
||||||
um types.UserManager
|
um types.UserManager
|
||||||
prefs *config.Preferences
|
settings *settings.Settings
|
||||||
|
|
||||||
authClient pmapi.Client
|
authClient pmapi.Client
|
||||||
auth *pmapi.Auth
|
auth *pmapi.Auth
|
||||||
|
|
||||||
LatestUserID string
|
LatestUserID string
|
||||||
accountMutex sync.Mutex
|
accountMutex sync.Mutex
|
||||||
|
restarter types.Restarter
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetupAccounts will create Model and set QMLer and UserManager
|
// SetupAccounts will create Model and set QMLer and UserManager
|
||||||
func (a *Accounts) SetupAccounts(qml QMLer, um types.UserManager) {
|
func (a *Accounts) SetupAccounts(qml QMLer, um types.UserManager, restarter types.Restarter) {
|
||||||
a.Model = NewAccountsModel(nil)
|
a.Model = NewAccountsModel(nil)
|
||||||
a.qml = qml
|
a.qml = qml
|
||||||
a.um = um
|
a.um = um
|
||||||
|
a.restarter = restarter
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadAccounts refreshes the current account list in GUI
|
// LoadAccounts refreshes the current account list in GUI
|
||||||
@ -102,9 +102,9 @@ func (a *Accounts) LoadAccounts() {
|
|||||||
accInfo.SetUserID(user.ID())
|
accInfo.SetUserID(user.ID())
|
||||||
accInfo.SetHostname(bridge.Host)
|
accInfo.SetHostname(bridge.Host)
|
||||||
accInfo.SetPassword(user.GetBridgePassword())
|
accInfo.SetPassword(user.GetBridgePassword())
|
||||||
if a.prefs != nil {
|
if a.settings != nil {
|
||||||
accInfo.SetPortIMAP(a.prefs.GetInt(preferences.IMAPPortKey))
|
accInfo.SetPortIMAP(a.settings.GetInt(settings.IMAPPortKey))
|
||||||
accInfo.SetPortSMTP(a.prefs.GetInt(preferences.SMTPPortKey))
|
accInfo.SetPortSMTP(a.settings.GetInt(settings.SMTPPortKey))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set aliases.
|
// Set aliases.
|
||||||
@ -127,7 +127,7 @@ func (a *Accounts) ClearCache() {
|
|||||||
}
|
}
|
||||||
// Clearing data removes everything (db, preferences, ...)
|
// Clearing data removes everything (db, preferences, ...)
|
||||||
// so everything has to be stopped and started again.
|
// so everything has to be stopped and started again.
|
||||||
a.qml.SetIsRestarting(true)
|
a.restarter.SetToRestart()
|
||||||
a.qml.Quit()
|
a.qml.Quit()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -137,7 +137,7 @@ func (a *Accounts) ClearKeychain() {
|
|||||||
for _, user := range a.um.GetUsers() {
|
for _, user := range a.um.GetUsers() {
|
||||||
if err := a.um.DeleteUser(user.ID(), false); err != nil {
|
if err := a.um.DeleteUser(user.ID(), false); err != nil {
|
||||||
log.Error("While deleting user: ", err)
|
log.Error("While deleting user: ", err)
|
||||||
if err == keychain.ErrNoKeychainInstalled { // Probably not needed anymore.
|
if err == keychain.ErrNoKeychain { // Probably not needed anymore.
|
||||||
a.qml.NotifyHasNoKeychain()
|
a.qml.NotifyHasNoKeychain()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -172,7 +172,6 @@ func (a *Accounts) showLoginError(err error, scope string) bool {
|
|||||||
}
|
}
|
||||||
a.qml.SetConnectionStatus(true) // If we are here connection is ok.
|
a.qml.SetConnectionStatus(true) // If we are here connection is ok.
|
||||||
if err == pmapi.ErrUpgradeApplication {
|
if err == pmapi.ErrUpgradeApplication {
|
||||||
a.qml.EmitEvent(events.UpgradeApplicationEvent, "")
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
a.qml.SetAddAccountWarning(err.Error(), -1)
|
a.qml.SetAddAccountWarning(err.Error(), -1)
|
||||||
@ -249,7 +248,7 @@ func (a *Accounts) DeleteAccount(iAccount int, removePreferences bool) {
|
|||||||
userID := a.Model.get(iAccount).UserID()
|
userID := a.Model.get(iAccount).UserID()
|
||||||
if err := a.um.DeleteUser(userID, removePreferences); err != nil {
|
if err := a.um.DeleteUser(userID, removePreferences); err != nil {
|
||||||
log.Warn("deleteUser: cannot remove user: ", err)
|
log.Warn("deleteUser: cannot remove user: ", err)
|
||||||
if err == keychain.ErrNoKeychainInstalled {
|
if err == keychain.ErrNoKeychain {
|
||||||
a.qml.NotifyHasNoKeychain()
|
a.qml.NotifyHasNoKeychain()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@ -111,10 +111,12 @@ func WaitForEnter() {
|
|||||||
|
|
||||||
type Listener interface {
|
type Listener interface {
|
||||||
Add(string, chan<- string)
|
Add(string, chan<- string)
|
||||||
|
RetryEmit(string)
|
||||||
}
|
}
|
||||||
|
|
||||||
func MakeAndRegisterEvent(eventListener Listener, event string) <-chan string {
|
func MakeAndRegisterEvent(eventListener Listener, event string) <-chan string {
|
||||||
ch := make(chan string)
|
ch := make(chan string)
|
||||||
eventListener.Add(event, ch)
|
eventListener.Add(event, ch)
|
||||||
|
eventListener.RetryEmit(event)
|
||||||
return ch
|
return ch
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,16 +22,16 @@ package qtie
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"sync"
|
||||||
"time"
|
|
||||||
|
|
||||||
|
"github.com/ProtonMail/proton-bridge/internal/config/settings"
|
||||||
"github.com/ProtonMail/proton-bridge/internal/events"
|
"github.com/ProtonMail/proton-bridge/internal/events"
|
||||||
qtcommon "github.com/ProtonMail/proton-bridge/internal/frontend/qt-common"
|
qtcommon "github.com/ProtonMail/proton-bridge/internal/frontend/qt-common"
|
||||||
"github.com/ProtonMail/proton-bridge/internal/frontend/types"
|
"github.com/ProtonMail/proton-bridge/internal/frontend/types"
|
||||||
"github.com/ProtonMail/proton-bridge/internal/importexport"
|
"github.com/ProtonMail/proton-bridge/internal/importexport"
|
||||||
|
"github.com/ProtonMail/proton-bridge/internal/locations"
|
||||||
"github.com/ProtonMail/proton-bridge/internal/transfer"
|
"github.com/ProtonMail/proton-bridge/internal/transfer"
|
||||||
"github.com/ProtonMail/proton-bridge/internal/updates"
|
"github.com/ProtonMail/proton-bridge/internal/updater"
|
||||||
"github.com/ProtonMail/proton-bridge/pkg/config"
|
|
||||||
"github.com/ProtonMail/proton-bridge/pkg/listener"
|
"github.com/ProtonMail/proton-bridge/pkg/listener"
|
||||||
|
|
||||||
"github.com/therecipe/qt/core"
|
"github.com/therecipe/qt/core"
|
||||||
@ -51,9 +51,10 @@ var log = logrus.WithField("pkg", "frontend-qt-ie")
|
|||||||
// Qt and QML objects. QML signals and slots are connected via methods of GoQMLInterface.
|
// Qt and QML objects. QML signals and slots are connected via methods of GoQMLInterface.
|
||||||
type FrontendQt struct {
|
type FrontendQt struct {
|
||||||
panicHandler types.PanicHandler
|
panicHandler types.PanicHandler
|
||||||
config *config.Config
|
locations *locations.Locations
|
||||||
|
settings *settings.Settings
|
||||||
eventListener listener.Listener
|
eventListener listener.Listener
|
||||||
updates types.Updater
|
updater types.Updater
|
||||||
ie types.ImportExporter
|
ie types.ImportExporter
|
||||||
|
|
||||||
App *widgets.QApplication // Main Application pointer
|
App *widgets.QApplication // Main Application pointer
|
||||||
@ -62,7 +63,7 @@ type FrontendQt struct {
|
|||||||
Qml *GoQMLInterface // Object accessible from both Go and QML for methods and signals
|
Qml *GoQMLInterface // Object accessible from both Go and QML for methods and signals
|
||||||
Accounts qtcommon.Accounts // Providing data for accounts ListView
|
Accounts qtcommon.Accounts // Providing data for accounts ListView
|
||||||
|
|
||||||
programName string // Program name
|
programName string // App name
|
||||||
programVersion string // Program version
|
programVersion string // Program version
|
||||||
buildVersion string // Program build version
|
buildVersion string // Program build version
|
||||||
|
|
||||||
@ -72,55 +73,80 @@ type FrontendQt struct {
|
|||||||
transfer *transfer.Transfer
|
transfer *transfer.Transfer
|
||||||
progress *transfer.Progress
|
progress *transfer.Progress
|
||||||
|
|
||||||
notifyHasNoKeychain bool
|
restarter types.Restarter
|
||||||
|
|
||||||
|
// saving most up-to-date update info to install it manually
|
||||||
|
updateInfo updater.VersionInfo
|
||||||
|
|
||||||
|
initializing sync.WaitGroup
|
||||||
|
initializationDone sync.Once
|
||||||
}
|
}
|
||||||
|
|
||||||
// New is constructor for Import-Export Qt-Go interface
|
// New is constructor for Import-Export Qt-Go interface
|
||||||
func New(
|
func New(
|
||||||
version, buildVersion string,
|
version, buildVersion, programName string,
|
||||||
panicHandler types.PanicHandler,
|
panicHandler types.PanicHandler,
|
||||||
config *config.Config,
|
locations *locations.Locations,
|
||||||
|
settings *settings.Settings,
|
||||||
eventListener listener.Listener,
|
eventListener listener.Listener,
|
||||||
updates types.Updater,
|
updater types.Updater,
|
||||||
ie types.ImportExporter,
|
ie types.ImportExporter,
|
||||||
|
restarter types.Restarter,
|
||||||
) *FrontendQt {
|
) *FrontendQt {
|
||||||
f := &FrontendQt{
|
f := &FrontendQt{
|
||||||
panicHandler: panicHandler,
|
panicHandler: panicHandler,
|
||||||
config: config,
|
locations: locations,
|
||||||
programName: "ProtonMail Import-Export",
|
settings: settings,
|
||||||
|
programName: programName,
|
||||||
programVersion: "v" + version,
|
programVersion: "v" + version,
|
||||||
eventListener: eventListener,
|
eventListener: eventListener,
|
||||||
|
updater: updater,
|
||||||
buildVersion: buildVersion,
|
buildVersion: buildVersion,
|
||||||
updates: updates,
|
|
||||||
ie: ie,
|
ie: ie,
|
||||||
|
restarter: restarter,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Initializing.Done is only called sync.Once. Please keep the increment
|
||||||
|
// set to 1
|
||||||
|
f.initializing.Add(1)
|
||||||
|
|
||||||
log.Debugf("New Qt frontend: %p", f)
|
log.Debugf("New Qt frontend: %p", f)
|
||||||
return f
|
return f
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsAppRestarting for Import-Export is always false i.e never restarts
|
|
||||||
func (f *FrontendQt) IsAppRestarting() bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Loop function for Import-Export interface. It runs QtExecute in main thread
|
// Loop function for Import-Export interface. It runs QtExecute in main thread
|
||||||
// with no additional function.
|
// with no additional function.
|
||||||
func (f *FrontendQt) Loop(setupError error) (err error) {
|
func (f *FrontendQt) Loop() (err error) {
|
||||||
if setupError != nil {
|
|
||||||
f.notifyHasNoKeychain = true
|
|
||||||
}
|
|
||||||
go func() {
|
|
||||||
defer f.panicHandler.HandlePanic()
|
|
||||||
f.watchEvents()
|
|
||||||
}()
|
|
||||||
err = f.QtExecute(func(f *FrontendQt) error { return nil })
|
err = f.QtExecute(func(f *FrontendQt) error { return nil })
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *FrontendQt) NotifyManualUpdate(update updater.VersionInfo, canInstall bool) {
|
||||||
|
f.SetVersion(update)
|
||||||
|
f.Qml.SetUpdateCanInstall(canInstall)
|
||||||
|
f.Qml.NotifyManualUpdate()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FrontendQt) SetVersion(version updater.VersionInfo) {
|
||||||
|
f.Qml.SetUpdateVersion(version.Version.String())
|
||||||
|
f.Qml.SetUpdateLandingPage(version.LandingPage)
|
||||||
|
f.Qml.SetUpdateReleaseNotesLink(version.ReleaseNotesPage)
|
||||||
|
f.updateInfo = version
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FrontendQt) NotifySilentUpdateInstalled() {
|
||||||
|
f.Qml.NotifySilentUpdateRestartNeeded()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FrontendQt) NotifySilentUpdateError(err error) {
|
||||||
|
f.Qml.NotifySilentUpdateError()
|
||||||
|
}
|
||||||
|
|
||||||
func (f *FrontendQt) watchEvents() {
|
func (f *FrontendQt) watchEvents() {
|
||||||
|
credentialsErrorCh := qtcommon.MakeAndRegisterEvent(f.eventListener, events.CredentialsErrorEvent)
|
||||||
internetOffCh := qtcommon.MakeAndRegisterEvent(f.eventListener, events.InternetOffEvent)
|
internetOffCh := qtcommon.MakeAndRegisterEvent(f.eventListener, events.InternetOffEvent)
|
||||||
internetOnCh := qtcommon.MakeAndRegisterEvent(f.eventListener, events.InternetOnEvent)
|
internetOnCh := qtcommon.MakeAndRegisterEvent(f.eventListener, events.InternetOnEvent)
|
||||||
|
secondInstanceCh := qtcommon.MakeAndRegisterEvent(f.eventListener, events.SecondInstanceEvent)
|
||||||
restartBridgeCh := qtcommon.MakeAndRegisterEvent(f.eventListener, events.RestartBridgeEvent)
|
restartBridgeCh := qtcommon.MakeAndRegisterEvent(f.eventListener, events.RestartBridgeEvent)
|
||||||
addressChangedCh := qtcommon.MakeAndRegisterEvent(f.eventListener, events.AddressChangedEvent)
|
addressChangedCh := qtcommon.MakeAndRegisterEvent(f.eventListener, events.AddressChangedEvent)
|
||||||
addressChangedLogoutCh := qtcommon.MakeAndRegisterEvent(f.eventListener, events.AddressChangedLogoutEvent)
|
addressChangedLogoutCh := qtcommon.MakeAndRegisterEvent(f.eventListener, events.AddressChangedLogoutEvent)
|
||||||
@ -129,12 +155,16 @@ func (f *FrontendQt) watchEvents() {
|
|||||||
newUserCh := qtcommon.MakeAndRegisterEvent(f.eventListener, events.UserRefreshEvent)
|
newUserCh := qtcommon.MakeAndRegisterEvent(f.eventListener, events.UserRefreshEvent)
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
|
case <-credentialsErrorCh:
|
||||||
|
f.Qml.NotifyHasNoKeychain()
|
||||||
case <-internetOffCh:
|
case <-internetOffCh:
|
||||||
f.Qml.SetConnectionStatus(false)
|
f.Qml.SetConnectionStatus(false)
|
||||||
case <-internetOnCh:
|
case <-internetOnCh:
|
||||||
f.Qml.SetConnectionStatus(true)
|
f.Qml.SetConnectionStatus(true)
|
||||||
|
case <-secondInstanceCh:
|
||||||
|
f.Qml.ShowWindow()
|
||||||
case <-restartBridgeCh:
|
case <-restartBridgeCh:
|
||||||
f.Qml.SetIsRestarting(true)
|
f.restarter.SetToRestart()
|
||||||
f.App.Quit()
|
f.App.Quit()
|
||||||
case address := <-addressChangedCh:
|
case address := <-addressChangedCh:
|
||||||
f.Qml.NotifyAddressChanged(address)
|
f.Qml.NotifyAddressChanged(address)
|
||||||
@ -148,7 +178,7 @@ func (f *FrontendQt) watchEvents() {
|
|||||||
f.Qml.NotifyLogout(user.Username())
|
f.Qml.NotifyLogout(user.Username())
|
||||||
case <-updateApplicationCh:
|
case <-updateApplicationCh:
|
||||||
f.Qml.ProcessFinished()
|
f.Qml.ProcessFinished()
|
||||||
f.Qml.NotifyUpdate()
|
f.Qml.NotifyForceUpdate()
|
||||||
case <-newUserCh:
|
case <-newUserCh:
|
||||||
f.Qml.LoadAccounts()
|
f.Qml.LoadAccounts()
|
||||||
}
|
}
|
||||||
@ -165,7 +195,7 @@ func (f *FrontendQt) qtSetupQmlAndStructures() {
|
|||||||
f.View.RootContext().SetContextProperty("go", f.Qml)
|
f.View.RootContext().SetContextProperty("go", f.Qml)
|
||||||
|
|
||||||
// Add AccountsModel
|
// Add AccountsModel
|
||||||
f.Accounts.SetupAccounts(f.Qml, f.ie)
|
f.Accounts.SetupAccounts(f.Qml, f.ie, f.restarter)
|
||||||
f.View.RootContext().SetContextProperty("accountsModel", f.Accounts.Model)
|
f.View.RootContext().SetContextProperty("accountsModel", f.Accounts.Model)
|
||||||
|
|
||||||
// Add TransferRules structure
|
// Add TransferRules structure
|
||||||
@ -189,11 +219,6 @@ func (f *FrontendQt) qtSetupQmlAndStructures() {
|
|||||||
} else {
|
} else {
|
||||||
f.Qml.SetIsFirstStart(false)
|
f.Qml.SetIsFirstStart(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Notify user about error during initialization.
|
|
||||||
if f.notifyHasNoKeychain {
|
|
||||||
f.Qml.NotifyHasNoKeychain()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// QtExecute in main for starting Qt application
|
// QtExecute in main for starting Qt application
|
||||||
@ -220,6 +245,17 @@ func (f *FrontendQt) QtExecute(Procedure func(*FrontendQt) error) error {
|
|||||||
f.Qml.SetCredits(importexport.Credits)
|
f.Qml.SetCredits(importexport.Credits)
|
||||||
f.Qml.SetFullversion(f.buildVersion)
|
f.Qml.SetFullversion(f.buildVersion)
|
||||||
|
|
||||||
|
if f.settings.GetBool(settings.AutoUpdateKey) {
|
||||||
|
f.Qml.SetIsAutoUpdate(true)
|
||||||
|
} else {
|
||||||
|
f.Qml.SetIsAutoUpdate(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer f.panicHandler.HandlePanic()
|
||||||
|
f.watchEvents()
|
||||||
|
}()
|
||||||
|
|
||||||
// Loop
|
// Loop
|
||||||
if ret := gui.QGuiApplication_Exec(); ret != 0 {
|
if ret := gui.QGuiApplication_Exec(); ret != 0 {
|
||||||
//err := errors.New(errors.ErrQApplication, "Event loop ended with return value: %v", string(ret))
|
//err := errors.New(errors.ErrQApplication, "Event loop ended with return value: %v", string(ret))
|
||||||
@ -233,7 +269,12 @@ func (f *FrontendQt) QtExecute(Procedure func(*FrontendQt) error) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (f *FrontendQt) openLogs() {
|
func (f *FrontendQt) openLogs() {
|
||||||
go open.Run(f.config.GetLogDir())
|
logsPath, err := f.locations.ProvideLogsPath()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
go open.Run(logsPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *FrontendQt) openReport() {
|
func (f *FrontendQt) openReport() {
|
||||||
@ -241,7 +282,7 @@ func (f *FrontendQt) openReport() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (f *FrontendQt) openDownloadLink() {
|
func (f *FrontendQt) openDownloadLink() {
|
||||||
go open.Run(f.updates.GetDownloadLink())
|
// NOTE: Fix this.
|
||||||
}
|
}
|
||||||
|
|
||||||
// sendImportReport sends an anonymized import or export report file to our customer support
|
// sendImportReport sends an anonymized import or export report file to our customer support
|
||||||
@ -279,6 +320,9 @@ func (f *FrontendQt) sendBug(description, emailClient, address string) bool {
|
|||||||
if f.Accounts.Model.Count() > 0 {
|
if f.Accounts.Model.Count() > 0 {
|
||||||
accname = f.Accounts.Model.Get(0).Account()
|
accname = f.Accounts.Model.Get(0).Account()
|
||||||
}
|
}
|
||||||
|
if accname == "" {
|
||||||
|
accname = "Unknown account"
|
||||||
|
}
|
||||||
|
|
||||||
if err := f.ie.ReportBug(
|
if err := f.ie.ReportBug(
|
||||||
core.QSysInfo_ProductType(),
|
core.QSysInfo_ProductType(),
|
||||||
@ -295,6 +339,18 @@ func (f *FrontendQt) sendBug(description, emailClient, address string) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *FrontendQt) toggleAutoUpdate() {
|
||||||
|
defer f.Qml.ProcessFinished()
|
||||||
|
|
||||||
|
if f.settings.GetBool(settings.AutoUpdateKey) {
|
||||||
|
f.settings.SetBool(settings.AutoUpdateKey, false)
|
||||||
|
f.Qml.SetIsAutoUpdate(false)
|
||||||
|
} else {
|
||||||
|
f.settings.SetBool(settings.AutoUpdateKey, true)
|
||||||
|
f.Qml.SetIsAutoUpdate(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// checkInternet is almost idetical to bridge
|
// checkInternet is almost idetical to bridge
|
||||||
func (f *FrontendQt) checkInternet() {
|
func (f *FrontendQt) checkInternet() {
|
||||||
f.Qml.SetConnectionStatus(f.ie.CheckConnection() == nil)
|
f.Qml.SetConnectionStatus(f.ie.CheckConnection() == nil)
|
||||||
@ -365,62 +421,59 @@ func (f *FrontendQt) setProgressManager(progress *transfer.Progress) {
|
|||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
// StartUpdate is identical to bridge
|
func (f *FrontendQt) startManualUpdate() {
|
||||||
func (f *FrontendQt) StartUpdate() {
|
|
||||||
progress := make(chan updates.Progress)
|
|
||||||
go func() { // Update progress in QML.
|
|
||||||
defer f.panicHandler.HandlePanic()
|
|
||||||
for current := range progress {
|
|
||||||
f.Qml.SetProgress(current.Processed)
|
|
||||||
f.Qml.SetProgressDescription(strconv.Itoa(current.Description))
|
|
||||||
// Error happend
|
|
||||||
if current.Err != nil {
|
|
||||||
log.Error("update progress: ", current.Err)
|
|
||||||
f.Qml.UpdateFinished(true)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Finished everything OK.
|
|
||||||
if current.Description >= updates.InfoQuitApp {
|
|
||||||
f.Qml.UpdateFinished(false)
|
|
||||||
time.Sleep(3 * time.Second) // Just notify.
|
|
||||||
f.Qml.SetIsRestarting(current.Description == updates.InfoRestartApp)
|
|
||||||
f.App.Quit()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
go func() {
|
go func() {
|
||||||
defer f.panicHandler.HandlePanic()
|
err := f.updater.InstallUpdate(f.updateInfo)
|
||||||
f.updates.StartUpgrade(progress)
|
|
||||||
|
if err != nil {
|
||||||
|
logrus.WithError(err).Error("An error occurred while installing updates manually")
|
||||||
|
f.Qml.NotifyManualUpdateError()
|
||||||
|
} else {
|
||||||
|
f.Qml.NotifyManualUpdateRestartNeeded()
|
||||||
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
// isNewVersionAvailable is identical to bridge
|
func (f *FrontendQt) checkIsLatestVersionAndUpdate() bool {
|
||||||
// return 0 when local version is fine
|
version, err := f.updater.Check()
|
||||||
// return 1 when new version is available
|
|
||||||
func (f *FrontendQt) isNewVersionAvailable(showMessage bool) {
|
if err != nil {
|
||||||
|
logrus.WithError(err).Error("An error occurred while checking updates manually")
|
||||||
|
f.Qml.NotifyManualUpdateError()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
f.SetVersion(version)
|
||||||
|
|
||||||
|
if !f.updater.IsUpdateApplicable(version) {
|
||||||
|
logrus.Debug("No need to update")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
logrus.WithField("version", version.Version).Info("An update is available")
|
||||||
|
|
||||||
|
if !f.updater.CanInstall(version) {
|
||||||
|
logrus.Debug("A manual update is required")
|
||||||
|
f.NotifyManualUpdate(version, false)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
f.NotifyManualUpdate(version, true)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *FrontendQt) checkAndOpenReleaseNotes() {
|
||||||
go func() {
|
go func() {
|
||||||
defer f.Qml.ProcessFinished()
|
_ = s.checkIsLatestVersionAndUpdate()
|
||||||
isUpToDate, latestVersionInfo, err := f.updates.CheckIsUpToDate()
|
s.Qml.OpenReleaseNotesExternally()
|
||||||
if err != nil {
|
}()
|
||||||
log.Warnln("Cannot retrieve version info: ", err)
|
}
|
||||||
f.checkInternet()
|
|
||||||
return
|
func (s *FrontendQt) checkForUpdates() {
|
||||||
|
go func() {
|
||||||
|
if s.checkIsLatestVersionAndUpdate() {
|
||||||
|
s.Qml.NotifyVersionIsTheLatest()
|
||||||
}
|
}
|
||||||
f.Qml.SetConnectionStatus(true) // if we are here connection is ok
|
|
||||||
if isUpToDate {
|
|
||||||
f.Qml.SetUpdateState(StatusUpToDate)
|
|
||||||
if showMessage {
|
|
||||||
f.Qml.NotifyVersionIsTheLatest()
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
f.Qml.SetNewversion(latestVersionInfo.Version)
|
|
||||||
f.Qml.SetChangelog(latestVersionInfo.ReleaseNotes)
|
|
||||||
f.Qml.SetBugfixes(latestVersionInfo.ReleaseFixedBugs)
|
|
||||||
f.Qml.SetLandingPage(latestVersionInfo.LandingPage)
|
|
||||||
f.Qml.SetDownloadLink(latestVersionInfo.GetDownloadLink())
|
|
||||||
f.Qml.SetUpdateState(StatusNewVersionAvailable)
|
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -434,16 +487,12 @@ func (f *FrontendQt) resetSource() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (f *FrontendQt) openLicenseFile() {
|
func (f *FrontendQt) openLicenseFile() {
|
||||||
go open.Run(f.config.GetLicenseFilePath())
|
go open.Run(f.locations.GetLicenseFilePath())
|
||||||
}
|
}
|
||||||
|
|
||||||
// getLocalVersionInfo is identical to bridge.
|
// getLocalVersionInfo is identical to bridge.
|
||||||
func (f *FrontendQt) getLocalVersionInfo() {
|
func (f *FrontendQt) getLocalVersionInfo() {
|
||||||
defer f.Qml.ProcessFinished()
|
// NOTE: Fix this.
|
||||||
localVersion := f.updates.GetLocalVersion()
|
|
||||||
f.Qml.SetNewversion(localVersion.Version)
|
|
||||||
f.Qml.SetChangelog(localVersion.ReleaseNotes)
|
|
||||||
f.Qml.SetBugfixes(localVersion.ReleaseFixedBugs)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// LeastUsedColor is intended to return color for creating a new inbox or label.
|
// LeastUsedColor is intended to return color for creating a new inbox or label.
|
||||||
@ -500,3 +549,14 @@ func (f *FrontendQt) createLabelOrFolder(email, name, color string, isLabel bool
|
|||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *FrontendQt) WaitUntilFrontendIsReady() {
|
||||||
|
f.initializing.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
// setGUIIsReady unlocks the WaitUntilFrontendIsReady.
|
||||||
|
func (f *FrontendQt) setGUIIsReady() {
|
||||||
|
f.initializationDone.Do(func() {
|
||||||
|
f.initializing.Done()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@ -23,8 +23,10 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/ProtonMail/proton-bridge/internal/config/settings"
|
||||||
"github.com/ProtonMail/proton-bridge/internal/frontend/types"
|
"github.com/ProtonMail/proton-bridge/internal/frontend/types"
|
||||||
"github.com/ProtonMail/proton-bridge/pkg/config"
|
"github.com/ProtonMail/proton-bridge/internal/locations"
|
||||||
|
"github.com/ProtonMail/proton-bridge/internal/updater"
|
||||||
"github.com/ProtonMail/proton-bridge/pkg/listener"
|
"github.com/ProtonMail/proton-bridge/pkg/listener"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
@ -33,23 +35,39 @@ var log = logrus.WithField("pkg", "frontend-nogui") //nolint[gochecknoglobals]
|
|||||||
|
|
||||||
type FrontendHeadless struct{}
|
type FrontendHeadless struct{}
|
||||||
|
|
||||||
func (s *FrontendHeadless) Loop(credentialsError error) error {
|
func (s *FrontendHeadless) Loop() error {
|
||||||
log.Info("Check status on localhost:8081")
|
log.Info("Check status on localhost:8082")
|
||||||
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
fmt.Fprintf(w, "IE is running")
|
fmt.Fprintf(w, "IE is running")
|
||||||
})
|
})
|
||||||
return http.ListenAndServe(":8081", nil)
|
return http.ListenAndServe(":8082", nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *FrontendHeadless) IsAppRestarting() bool { return false }
|
func (s *FrontendHeadless) NotifyManualUpdate(update updater.VersionInfo, canInstall bool) {
|
||||||
|
// NOTE: Save the update somewhere so that it can be installed when user chooses "install now".
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *FrontendHeadless) SetVersion(update updater.VersionInfo) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *FrontendHeadless) WaitUntilFrontendIsReady() {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *FrontendHeadless) NotifySilentUpdateInstalled() {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *FrontendHeadless) NotifySilentUpdateError(err error) {
|
||||||
|
}
|
||||||
|
|
||||||
func New(
|
func New(
|
||||||
version, buildVersion string,
|
version, buildVersion, appName string,
|
||||||
panicHandler types.PanicHandler,
|
panicHandler types.PanicHandler,
|
||||||
config *config.Config,
|
locations *locations.Locations,
|
||||||
|
settings *settings.Settings,
|
||||||
eventListener listener.Listener,
|
eventListener listener.Listener,
|
||||||
updates types.Updater,
|
updater types.Updater,
|
||||||
ie types.ImportExporter,
|
ie types.ImportExporter,
|
||||||
|
restarter types.Restarter,
|
||||||
) *FrontendHeadless {
|
) *FrontendHeadless {
|
||||||
return &FrontendHeadless{}
|
return &FrontendHeadless{}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -33,11 +33,11 @@ type GoQMLInterface struct {
|
|||||||
|
|
||||||
_ func() `constructor:"init"`
|
_ func() `constructor:"init"`
|
||||||
|
|
||||||
|
_ bool `property:"isAutoUpdate"`
|
||||||
_ string `property:"currentAddress"`
|
_ string `property:"currentAddress"`
|
||||||
_ string `property:"goos"`
|
_ string `property:"goos"`
|
||||||
_ string `property:"credits"`
|
_ string `property:"credits"`
|
||||||
_ bool `property:"isFirstStart"`
|
_ bool `property:"isFirstStart"`
|
||||||
_ bool `property:"isRestarting"`
|
|
||||||
_ bool `property:"isConnectionOK"`
|
_ bool `property:"isConnectionOK"`
|
||||||
|
|
||||||
_ string `property:lastError`
|
_ string `property:lastError`
|
||||||
@ -50,12 +50,24 @@ type GoQMLInterface struct {
|
|||||||
_ string `property:importLogFileName`
|
_ string `property:importLogFileName`
|
||||||
|
|
||||||
_ string `property:"programTitle"`
|
_ string `property:"programTitle"`
|
||||||
_ string `property:"newversion"`
|
|
||||||
_ string `property:"fullversion"`
|
_ string `property:"fullversion"`
|
||||||
_ string `property:"downloadLink"`
|
_ string `property:"downloadLink"`
|
||||||
_ string `property:"landingPage"`
|
|
||||||
_ string `property:"changelog"`
|
_ string `property:"updateVersion"`
|
||||||
_ string `property:"bugfixes"`
|
_ bool `property:"updateCanInstall"`
|
||||||
|
_ string `property:"updateLandingPage"`
|
||||||
|
_ string `property:"updateReleaseNotesLink"`
|
||||||
|
_ func() `signal:"notifyManualUpdate"`
|
||||||
|
_ func() `signal:"notifyManualUpdateRestartNeeded"`
|
||||||
|
_ func() `signal:"notifyManualUpdateError"`
|
||||||
|
_ func() `signal:"notifyForceUpdate"`
|
||||||
|
_ func() `signal:"notifySilentUpdateRestartNeeded"`
|
||||||
|
_ func() `signal:"notifySilentUpdateError"`
|
||||||
|
_ func() `slot:"checkForUpdates"`
|
||||||
|
_ func() `slot:"checkAndOpenReleaseNotes"`
|
||||||
|
_ func() `signal:"openReleaseNotesExternally"`
|
||||||
|
_ func() `slot:"startManualUpdate"`
|
||||||
|
_ func() `slot:"guiIsReady"`
|
||||||
|
|
||||||
// translations
|
// translations
|
||||||
_ string `property:"wrongCredentials"`
|
_ string `property:"wrongCredentials"`
|
||||||
@ -68,6 +80,8 @@ type GoQMLInterface struct {
|
|||||||
_ func(updateState string) `signal:"setUpdateState"`
|
_ func(updateState string) `signal:"setUpdateState"`
|
||||||
_ func() `slot:"checkInternet"`
|
_ func() `slot:"checkInternet"`
|
||||||
|
|
||||||
|
_ func() `slot:"setToRestart"`
|
||||||
|
|
||||||
_ func() `signal:"processFinished"`
|
_ func() `signal:"processFinished"`
|
||||||
_ func(okay bool) `signal:"exportStructureLoadFinished"`
|
_ func(okay bool) `signal:"exportStructureLoadFinished"`
|
||||||
_ func(okay bool) `signal:"importStructuresLoadFinished"`
|
_ func(okay bool) `signal:"importStructuresLoadFinished"`
|
||||||
@ -77,6 +91,9 @@ type GoQMLInterface struct {
|
|||||||
_ func() `slot:"getLocalVersionInfo"`
|
_ func() `slot:"getLocalVersionInfo"`
|
||||||
_ func() `slot:"loadImportReports"`
|
_ func() `slot:"loadImportReports"`
|
||||||
|
|
||||||
|
_ func() `signal:"showWindow"`
|
||||||
|
|
||||||
|
_ func() `slot:"toggleAutoUpdate"`
|
||||||
_ func() `slot:"quit"`
|
_ func() `slot:"quit"`
|
||||||
_ func() `slot:"loadAccounts"`
|
_ func() `slot:"loadAccounts"`
|
||||||
_ func() `slot:"openLogs"`
|
_ func() `slot:"openLogs"`
|
||||||
@ -87,8 +104,7 @@ type GoQMLInterface struct {
|
|||||||
_ func() `signal:"highlightSystray"`
|
_ func() `signal:"highlightSystray"`
|
||||||
_ func() `signal:"normalSystray"`
|
_ func() `signal:"normalSystray"`
|
||||||
|
|
||||||
_ func(showMessage bool) `slot:"isNewVersionAvailable"`
|
_ func() string `slot:"getBackendVersion"`
|
||||||
_ func() string `slot:"getBackendVersion"`
|
|
||||||
|
|
||||||
_ func(description, client, address string) bool `slot:"sendBug"`
|
_ func(description, client, address string) bool `slot:"sendBug"`
|
||||||
_ func(address string) bool `slot:"sendImportReport"`
|
_ func(address string) bool `slot:"sendImportReport"`
|
||||||
@ -126,12 +142,10 @@ type GoQMLInterface struct {
|
|||||||
_ func() `signal:"notifyVersionIsTheLatest"`
|
_ func() `signal:"notifyVersionIsTheLatest"`
|
||||||
_ func() `signal:"notifyKeychainRebuild"`
|
_ func() `signal:"notifyKeychainRebuild"`
|
||||||
_ func() `signal:"notifyHasNoKeychain"`
|
_ func() `signal:"notifyHasNoKeychain"`
|
||||||
_ func() `signal:"notifyUpdate"`
|
|
||||||
_ func(accname string) `signal:"notifyLogout"`
|
_ func(accname string) `signal:"notifyLogout"`
|
||||||
_ func(accname string) `signal:"notifyAddressChanged"`
|
_ func(accname string) `signal:"notifyAddressChanged"`
|
||||||
_ func(accname string) `signal:"notifyAddressChangedLogout"`
|
_ func(accname string) `signal:"notifyAddressChangedLogout"`
|
||||||
|
|
||||||
_ func() `slot:"startUpdate"`
|
|
||||||
_ func(hasError bool) `signal:"updateFinished"`
|
_ func(hasError bool) `signal:"updateFinished"`
|
||||||
|
|
||||||
// errors
|
// errors
|
||||||
@ -148,6 +162,7 @@ func (s *GoQMLInterface) init() {}
|
|||||||
func (s *GoQMLInterface) SetFrontend(f *FrontendQt) {
|
func (s *GoQMLInterface) SetFrontend(f *FrontendQt) {
|
||||||
s.ConnectQuit(f.App.Quit)
|
s.ConnectQuit(f.App.Quit)
|
||||||
|
|
||||||
|
s.ConnectToggleAutoUpdate(f.toggleAutoUpdate)
|
||||||
s.ConnectLoadAccounts(f.Accounts.LoadAccounts)
|
s.ConnectLoadAccounts(f.Accounts.LoadAccounts)
|
||||||
s.ConnectOpenLogs(f.openLogs)
|
s.ConnectOpenLogs(f.openLogs)
|
||||||
s.ConnectOpenDownloadLink(f.openDownloadLink)
|
s.ConnectOpenDownloadLink(f.openDownloadLink)
|
||||||
@ -165,18 +180,19 @@ func (s *GoQMLInterface) SetFrontend(f *FrontendQt) {
|
|||||||
s.ConnectAddAccount(f.Accounts.AddAccount)
|
s.ConnectAddAccount(f.Accounts.AddAccount)
|
||||||
|
|
||||||
s.SetGoos(runtime.GOOS)
|
s.SetGoos(runtime.GOOS)
|
||||||
s.SetIsRestarting(false)
|
|
||||||
s.SetProgramTitle(f.programName)
|
s.SetProgramTitle(f.programName)
|
||||||
|
|
||||||
s.ConnectOpenLicenseFile(f.openLicenseFile)
|
s.ConnectOpenLicenseFile(f.openLicenseFile)
|
||||||
s.ConnectGetLocalVersionInfo(f.getLocalVersionInfo)
|
s.ConnectGetLocalVersionInfo(f.getLocalVersionInfo)
|
||||||
s.ConnectIsNewVersionAvailable(f.isNewVersionAvailable)
|
s.ConnectCheckForUpdates(f.checkForUpdates)
|
||||||
s.ConnectGetBackendVersion(func() string {
|
s.ConnectGetBackendVersion(func() string {
|
||||||
return f.programVersion
|
return f.programVersion
|
||||||
})
|
})
|
||||||
|
|
||||||
s.ConnectCheckInternet(f.checkInternet)
|
s.ConnectCheckInternet(f.checkInternet)
|
||||||
|
|
||||||
|
s.ConnectSetToRestart(f.restarter.SetToRestart)
|
||||||
|
|
||||||
s.ConnectLoadStructureForExport(f.LoadStructureForExport)
|
s.ConnectLoadStructureForExport(f.LoadStructureForExport)
|
||||||
s.ConnectSetupAndLoadForImport(f.setupAndLoadForImport)
|
s.ConnectSetupAndLoadForImport(f.setupAndLoadForImport)
|
||||||
s.ConnectResetSource(f.resetSource)
|
s.ConnectResetSource(f.resetSource)
|
||||||
@ -186,9 +202,9 @@ func (s *GoQMLInterface) SetFrontend(f *FrontendQt) {
|
|||||||
s.ConnectStartExport(f.StartExport)
|
s.ConnectStartExport(f.StartExport)
|
||||||
s.ConnectStartImport(f.StartImport)
|
s.ConnectStartImport(f.StartImport)
|
||||||
|
|
||||||
s.ConnectCheckPathStatus(CheckPathStatus)
|
s.ConnectGuiIsReady(f.setGUIIsReady)
|
||||||
|
|
||||||
s.ConnectStartUpdate(f.StartUpdate)
|
s.ConnectCheckPathStatus(CheckPathStatus)
|
||||||
|
|
||||||
s.ConnectEmitEvent(f.emitEvent)
|
s.ConnectEmitEvent(f.emitEvent)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -24,8 +24,9 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/ProtonMail/proton-bridge/internal/bridge"
|
"github.com/ProtonMail/proton-bridge/internal/bridge"
|
||||||
|
"github.com/ProtonMail/proton-bridge/internal/config/settings"
|
||||||
"github.com/ProtonMail/proton-bridge/internal/events"
|
"github.com/ProtonMail/proton-bridge/internal/events"
|
||||||
"github.com/ProtonMail/proton-bridge/internal/preferences"
|
"github.com/ProtonMail/proton-bridge/internal/updater"
|
||||||
"github.com/ProtonMail/proton-bridge/pkg/keychain"
|
"github.com/ProtonMail/proton-bridge/pkg/keychain"
|
||||||
pmapi "github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
pmapi "github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
||||||
)
|
)
|
||||||
@ -63,8 +64,8 @@ func (s *FrontendQt) loadAccounts() {
|
|||||||
acc_info.SetUserID(user.ID())
|
acc_info.SetUserID(user.ID())
|
||||||
acc_info.SetHostname(bridge.Host)
|
acc_info.SetHostname(bridge.Host)
|
||||||
acc_info.SetPassword(user.GetBridgePassword())
|
acc_info.SetPassword(user.GetBridgePassword())
|
||||||
acc_info.SetPortIMAP(s.preferences.GetInt(preferences.IMAPPortKey))
|
acc_info.SetPortIMAP(s.settings.GetInt(settings.IMAPPortKey))
|
||||||
acc_info.SetPortSMTP(s.preferences.GetInt(preferences.SMTPPortKey))
|
acc_info.SetPortSMTP(s.settings.GetInt(settings.SMTPPortKey))
|
||||||
|
|
||||||
// Set aliases.
|
// Set aliases.
|
||||||
acc_info.SetAliases(strings.Join(user.GetAddresses(), ";"))
|
acc_info.SetAliases(strings.Join(user.GetAddresses(), ";"))
|
||||||
@ -80,12 +81,21 @@ func (s *FrontendQt) loadAccounts() {
|
|||||||
|
|
||||||
func (s *FrontendQt) clearCache() {
|
func (s *FrontendQt) clearCache() {
|
||||||
defer s.Qml.ProcessFinished()
|
defer s.Qml.ProcessFinished()
|
||||||
|
|
||||||
|
channel := s.bridge.GetUpdateChannel()
|
||||||
|
if channel == updater.EarlyChannel {
|
||||||
|
if err := s.bridge.SetUpdateChannel(updater.StableChannel); err != nil {
|
||||||
|
s.Qml.NotifyManualUpdateError()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if err := s.bridge.ClearData(); err != nil {
|
if err := s.bridge.ClearData(); err != nil {
|
||||||
log.Error("While clearing cache: ", err)
|
log.Error("While clearing cache: ", err)
|
||||||
}
|
}
|
||||||
// Clearing data removes everything (db, preferences, ...)
|
// Clearing data removes everything (db, preferences, ...)
|
||||||
// so everything has to be stopped and started again.
|
// so everything has to be stopped and started again.
|
||||||
s.Qml.SetIsRestarting(true)
|
s.restarter.SetToRestart()
|
||||||
s.App.Quit()
|
s.App.Quit()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -94,7 +104,7 @@ func (s *FrontendQt) clearKeychain() {
|
|||||||
for _, user := range s.bridge.GetUsers() {
|
for _, user := range s.bridge.GetUsers() {
|
||||||
if err := s.bridge.DeleteUser(user.ID(), false); err != nil {
|
if err := s.bridge.DeleteUser(user.ID(), false); err != nil {
|
||||||
log.Error("While deleting user: ", err)
|
log.Error("While deleting user: ", err)
|
||||||
if err == keychain.ErrNoKeychainInstalled { // Probably not needed anymore.
|
if err == keychain.ErrNoKeychain { // Probably not needed anymore.
|
||||||
s.Qml.NotifyHasNoKeychain()
|
s.Qml.NotifyHasNoKeychain()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -128,7 +138,6 @@ func (s *FrontendQt) showLoginError(err error, scope string) bool {
|
|||||||
}
|
}
|
||||||
s.Qml.SetConnectionStatus(true) // If we are here connection is ok.
|
s.Qml.SetConnectionStatus(true) // If we are here connection is ok.
|
||||||
if err == pmapi.ErrUpgradeApplication {
|
if err == pmapi.ErrUpgradeApplication {
|
||||||
s.eventListener.Emit(events.UpgradeApplicationEvent, "")
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
s.Qml.SetAddAccountWarning(err.Error(), -1)
|
s.Qml.SetAddAccountWarning(err.Error(), -1)
|
||||||
@ -203,7 +212,7 @@ func (s *FrontendQt) deleteAccount(iAccount int, removePreferences bool) {
|
|||||||
userID := s.Accounts.get(iAccount).UserID()
|
userID := s.Accounts.get(iAccount).UserID()
|
||||||
if err := s.bridge.DeleteUser(userID, removePreferences); err != nil {
|
if err := s.bridge.DeleteUser(userID, removePreferences); err != nil {
|
||||||
log.Warn("deleteUser: cannot remove user: ", err)
|
log.Warn("deleteUser: cannot remove user: ", err)
|
||||||
if err == keychain.ErrNoKeychainInstalled {
|
if err == keychain.ErrNoKeychain {
|
||||||
s.Qml.NotifyHasNoKeychain()
|
s.Qml.NotifyHasNoKeychain()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@ -38,18 +38,17 @@ import (
|
|||||||
|
|
||||||
"github.com/ProtonMail/go-autostart"
|
"github.com/ProtonMail/go-autostart"
|
||||||
"github.com/ProtonMail/proton-bridge/internal/bridge"
|
"github.com/ProtonMail/proton-bridge/internal/bridge"
|
||||||
|
"github.com/ProtonMail/proton-bridge/internal/config/settings"
|
||||||
"github.com/ProtonMail/proton-bridge/internal/events"
|
"github.com/ProtonMail/proton-bridge/internal/events"
|
||||||
"github.com/ProtonMail/proton-bridge/internal/frontend/autoconfig"
|
"github.com/ProtonMail/proton-bridge/internal/frontend/autoconfig"
|
||||||
qtcommon "github.com/ProtonMail/proton-bridge/internal/frontend/qt-common"
|
qtcommon "github.com/ProtonMail/proton-bridge/internal/frontend/qt-common"
|
||||||
"github.com/ProtonMail/proton-bridge/internal/frontend/types"
|
"github.com/ProtonMail/proton-bridge/internal/frontend/types"
|
||||||
"github.com/ProtonMail/proton-bridge/internal/preferences"
|
"github.com/ProtonMail/proton-bridge/internal/locations"
|
||||||
"github.com/ProtonMail/proton-bridge/internal/updates"
|
"github.com/ProtonMail/proton-bridge/internal/updater"
|
||||||
"github.com/ProtonMail/proton-bridge/pkg/config"
|
|
||||||
"github.com/ProtonMail/proton-bridge/pkg/listener"
|
"github.com/ProtonMail/proton-bridge/pkg/listener"
|
||||||
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
||||||
"github.com/ProtonMail/proton-bridge/pkg/ports"
|
"github.com/ProtonMail/proton-bridge/pkg/ports"
|
||||||
"github.com/ProtonMail/proton-bridge/pkg/useragent"
|
"github.com/ProtonMail/proton-bridge/pkg/useragent"
|
||||||
"github.com/kardianos/osext"
|
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/skratchdot/open-golang/open"
|
"github.com/skratchdot/open-golang/open"
|
||||||
"github.com/therecipe/qt/core"
|
"github.com/therecipe/qt/core"
|
||||||
@ -68,77 +67,77 @@ var accountMutex = &sync.Mutex{}
|
|||||||
type FrontendQt struct {
|
type FrontendQt struct {
|
||||||
version string
|
version string
|
||||||
buildVersion string
|
buildVersion string
|
||||||
|
programName string
|
||||||
showWindowOnStart bool
|
showWindowOnStart bool
|
||||||
panicHandler types.PanicHandler
|
panicHandler types.PanicHandler
|
||||||
config *config.Config
|
locations *locations.Locations
|
||||||
preferences *config.Preferences
|
settings *settings.Settings
|
||||||
eventListener listener.Listener
|
eventListener listener.Listener
|
||||||
updates types.Updater
|
updater types.Updater
|
||||||
bridge types.Bridger
|
bridge types.Bridger
|
||||||
noEncConfirmator types.NoEncConfirmator
|
noEncConfirmator types.NoEncConfirmator
|
||||||
|
|
||||||
App *widgets.QApplication // Main Application pointer.
|
App *widgets.QApplication // Main Application pointer.
|
||||||
View *qml.QQmlApplicationEngine // QML engine pointer.
|
View *qml.QQmlApplicationEngine // QML engine pointer.
|
||||||
MainWin *core.QObject // Pointer to main window inside QML.
|
MainWin *core.QObject // Pointer to main window inside QML.
|
||||||
Qml *GoQMLInterface // Object accessible from both Go and QML for methods and signals.
|
Qml *GoQMLInterface // Object accessible from both Go and QML for methods and signals.
|
||||||
Accounts *AccountsModel // Providing data for accounts ListView.
|
Accounts *AccountsModel // Providing data for accounts ListView.
|
||||||
programName string // Program name (shown in taskbar).
|
programVer string // Program version (shown in help).
|
||||||
programVer string // Program version (shown in help).
|
|
||||||
|
|
||||||
authClient pmapi.Client
|
authClient pmapi.Client
|
||||||
|
|
||||||
auth *pmapi.Auth
|
auth *pmapi.Auth
|
||||||
|
|
||||||
AutostartEntry *autostart.App
|
autostart *autostart.App
|
||||||
|
|
||||||
// expand userID when added
|
// expand userID when added
|
||||||
userIDAdded string
|
userIDAdded string
|
||||||
|
|
||||||
notifyHasNoKeychain bool
|
restarter types.Restarter
|
||||||
|
|
||||||
|
// saving most up-to-date update info to install it manually
|
||||||
|
updateInfo updater.VersionInfo
|
||||||
|
|
||||||
|
initializing sync.WaitGroup
|
||||||
|
initializationDone sync.Once
|
||||||
}
|
}
|
||||||
|
|
||||||
// New returns a new Qt frontendend for the bridge.
|
// New returns a new Qt frontend for the bridge.
|
||||||
func New(
|
func New(
|
||||||
version,
|
version,
|
||||||
buildVersion string,
|
buildVersion,
|
||||||
|
programName string,
|
||||||
showWindowOnStart bool,
|
showWindowOnStart bool,
|
||||||
panicHandler types.PanicHandler,
|
panicHandler types.PanicHandler,
|
||||||
config *config.Config,
|
locations *locations.Locations,
|
||||||
preferences *config.Preferences,
|
settings *settings.Settings,
|
||||||
eventListener listener.Listener,
|
eventListener listener.Listener,
|
||||||
updates types.Updater,
|
updater types.Updater,
|
||||||
bridge types.Bridger,
|
bridge types.Bridger,
|
||||||
noEncConfirmator types.NoEncConfirmator,
|
noEncConfirmator types.NoEncConfirmator,
|
||||||
|
autostart *autostart.App,
|
||||||
|
restarter types.Restarter,
|
||||||
) *FrontendQt {
|
) *FrontendQt {
|
||||||
prgName := "ProtonMail Bridge"
|
|
||||||
tmp := &FrontendQt{
|
tmp := &FrontendQt{
|
||||||
version: version,
|
version: version,
|
||||||
buildVersion: buildVersion,
|
buildVersion: buildVersion,
|
||||||
|
programName: programName,
|
||||||
showWindowOnStart: showWindowOnStart,
|
showWindowOnStart: showWindowOnStart,
|
||||||
panicHandler: panicHandler,
|
panicHandler: panicHandler,
|
||||||
config: config,
|
locations: locations,
|
||||||
preferences: preferences,
|
settings: settings,
|
||||||
eventListener: eventListener,
|
eventListener: eventListener,
|
||||||
updates: updates,
|
updater: updater,
|
||||||
bridge: bridge,
|
bridge: bridge,
|
||||||
noEncConfirmator: noEncConfirmator,
|
noEncConfirmator: noEncConfirmator,
|
||||||
|
programVer: "v" + version,
|
||||||
programName: prgName,
|
autostart: autostart,
|
||||||
programVer: "v" + version,
|
restarter: restarter,
|
||||||
AutostartEntry: &autostart.App{
|
|
||||||
Name: prgName,
|
|
||||||
DisplayName: prgName,
|
|
||||||
Exec: []string{"", "--no-window"},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle autostart if wanted.
|
// Initializing.Done is only called sync.Once. Please keep the increment
|
||||||
if p, err := osext.Executable(); err == nil {
|
// set to 1
|
||||||
tmp.AutostartEntry.Exec[0] = p
|
tmp.initializing.Add(1)
|
||||||
log.Info("Autostart ", p)
|
|
||||||
} else {
|
|
||||||
log.Error("Cannot get current executable path: ", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Nicer string for OS.
|
// Nicer string for OS.
|
||||||
currentOS := core.QSysInfo_PrettyProductName()
|
currentOS := core.QSysInfo_PrettyProductName()
|
||||||
@ -161,20 +160,37 @@ func (s *FrontendQt) InstanceExistAlert() {
|
|||||||
// Loop function for Bridge interface.
|
// Loop function for Bridge interface.
|
||||||
//
|
//
|
||||||
// It runs QtExecute in main thread with no additional function.
|
// It runs QtExecute in main thread with no additional function.
|
||||||
func (s *FrontendQt) Loop(credentialsError error) (err error) {
|
func (s *FrontendQt) Loop() (err error) {
|
||||||
if credentialsError != nil {
|
|
||||||
s.notifyHasNoKeychain = true
|
|
||||||
}
|
|
||||||
go func() {
|
|
||||||
defer s.panicHandler.HandlePanic()
|
|
||||||
s.watchEvents()
|
|
||||||
}()
|
|
||||||
err = s.qtExecute(func(s *FrontendQt) error { return nil })
|
err = s.qtExecute(func(s *FrontendQt) error { return nil })
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *FrontendQt) NotifyManualUpdate(update updater.VersionInfo, canInstall bool) {
|
||||||
|
s.SetVersion(update)
|
||||||
|
s.Qml.SetUpdateCanInstall(canInstall)
|
||||||
|
s.Qml.NotifyManualUpdate()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *FrontendQt) SetVersion(version updater.VersionInfo) {
|
||||||
|
s.Qml.SetUpdateVersion(version.Version.String())
|
||||||
|
s.Qml.SetUpdateLandingPage(version.LandingPage)
|
||||||
|
s.Qml.SetUpdateReleaseNotesLink(version.ReleaseNotesPage)
|
||||||
|
s.updateInfo = version
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *FrontendQt) NotifySilentUpdateInstalled() {
|
||||||
|
s.Qml.NotifySilentUpdateRestartNeeded()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *FrontendQt) NotifySilentUpdateError(err error) {
|
||||||
|
s.Qml.NotifySilentUpdateError()
|
||||||
|
}
|
||||||
|
|
||||||
func (s *FrontendQt) watchEvents() {
|
func (s *FrontendQt) watchEvents() {
|
||||||
|
s.WaitUntilFrontendIsReady()
|
||||||
|
|
||||||
errorCh := s.getEventChannel(events.ErrorEvent)
|
errorCh := s.getEventChannel(events.ErrorEvent)
|
||||||
|
credentialsErrorCh := s.getEventChannel(events.CredentialsErrorEvent)
|
||||||
outgoingNoEncCh := s.getEventChannel(events.OutgoingNoEncEvent)
|
outgoingNoEncCh := s.getEventChannel(events.OutgoingNoEncEvent)
|
||||||
noActiveKeyForRecipientCh := s.getEventChannel(events.NoActiveKeyForRecipientEvent)
|
noActiveKeyForRecipientCh := s.getEventChannel(events.NoActiveKeyForRecipientEvent)
|
||||||
internetOffCh := s.getEventChannel(events.InternetOffEvent)
|
internetOffCh := s.getEventChannel(events.InternetOffEvent)
|
||||||
@ -193,6 +209,8 @@ func (s *FrontendQt) watchEvents() {
|
|||||||
imapIssue := strings.Contains(errorDetails, "IMAP failed")
|
imapIssue := strings.Contains(errorDetails, "IMAP failed")
|
||||||
smtpIssue := strings.Contains(errorDetails, "SMTP failed")
|
smtpIssue := strings.Contains(errorDetails, "SMTP failed")
|
||||||
s.Qml.NotifyPortIssue(imapIssue, smtpIssue)
|
s.Qml.NotifyPortIssue(imapIssue, smtpIssue)
|
||||||
|
case <-credentialsErrorCh:
|
||||||
|
s.Qml.NotifyHasNoKeychain()
|
||||||
case idAndSubject := <-outgoingNoEncCh:
|
case idAndSubject := <-outgoingNoEncCh:
|
||||||
idAndSubjectSlice := strings.SplitN(idAndSubject, ":", 2)
|
idAndSubjectSlice := strings.SplitN(idAndSubject, ":", 2)
|
||||||
messageID := idAndSubjectSlice[0]
|
messageID := idAndSubjectSlice[0]
|
||||||
@ -207,7 +225,7 @@ func (s *FrontendQt) watchEvents() {
|
|||||||
case <-secondInstanceCh:
|
case <-secondInstanceCh:
|
||||||
s.Qml.ShowWindow()
|
s.Qml.ShowWindow()
|
||||||
case <-restartBridgeCh:
|
case <-restartBridgeCh:
|
||||||
s.Qml.SetIsRestarting(true)
|
s.restarter.SetToRestart()
|
||||||
// watchEvents is started in parallel with the Qt app.
|
// watchEvents is started in parallel with the Qt app.
|
||||||
// If the event comes too early, app might not be ready yet.
|
// If the event comes too early, app might not be ready yet.
|
||||||
if s.App != nil {
|
if s.App != nil {
|
||||||
@ -225,7 +243,7 @@ func (s *FrontendQt) watchEvents() {
|
|||||||
s.Qml.NotifyLogout(user.Username())
|
s.Qml.NotifyLogout(user.Username())
|
||||||
case <-updateApplicationCh:
|
case <-updateApplicationCh:
|
||||||
s.Qml.ProcessFinished()
|
s.Qml.ProcessFinished()
|
||||||
s.Qml.NotifyUpdate()
|
s.Qml.NotifyForceUpdate()
|
||||||
case <-newUserCh:
|
case <-newUserCh:
|
||||||
s.Qml.LoadAccounts()
|
s.Qml.LoadAccounts()
|
||||||
case <-certIssue:
|
case <-certIssue:
|
||||||
@ -237,6 +255,7 @@ func (s *FrontendQt) watchEvents() {
|
|||||||
func (s *FrontendQt) getEventChannel(event string) <-chan string {
|
func (s *FrontendQt) getEventChannel(event string) <-chan string {
|
||||||
ch := make(chan string)
|
ch := make(chan string)
|
||||||
s.eventListener.Add(event, ch)
|
s.eventListener.Add(event, ch)
|
||||||
|
s.eventListener.RetryEmit(event)
|
||||||
return ch
|
return ch
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -267,10 +286,6 @@ func (s *FrontendQt) Start() (err error) {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *FrontendQt) IsAppRestarting() bool {
|
|
||||||
return s.Qml.IsRestarting()
|
|
||||||
}
|
|
||||||
|
|
||||||
// InvMethod runs the function with name `method` defined in RootObject of the QML.
|
// InvMethod runs the function with name `method` defined in RootObject of the QML.
|
||||||
// Used for tests.
|
// Used for tests.
|
||||||
func (s *FrontendQt) InvMethod(method string) error {
|
func (s *FrontendQt) InvMethod(method string) error {
|
||||||
@ -304,13 +319,13 @@ func (s *FrontendQt) qtExecute(Procedure func(*FrontendQt) error) error {
|
|||||||
s.View.RootContext().SetContextProperty("go", s.Qml)
|
s.View.RootContext().SetContextProperty("go", s.Qml)
|
||||||
|
|
||||||
// Set first start flag.
|
// Set first start flag.
|
||||||
s.Qml.SetIsFirstStart(s.preferences.GetBool(preferences.FirstStartGUIKey))
|
s.Qml.SetIsFirstStart(s.settings.GetBool(settings.FirstStartGUIKey))
|
||||||
s.preferences.SetBool(preferences.FirstStartGUIKey, false)
|
s.settings.SetBool(settings.FirstStartGUIKey, false)
|
||||||
|
|
||||||
// Check if it is first start after update (fresh version).
|
// Check if it is first start after update (fresh version).
|
||||||
lastVersion := s.preferences.Get(preferences.LastVersionKey)
|
lastVersion := s.settings.Get(settings.LastVersionKey)
|
||||||
s.Qml.SetIsFreshVersion(lastVersion != "" && s.version != lastVersion)
|
s.Qml.SetIsFreshVersion(lastVersion != "" && s.version != lastVersion)
|
||||||
s.preferences.Set(preferences.LastVersionKey, s.version)
|
s.settings.Set(settings.LastVersionKey, s.version)
|
||||||
|
|
||||||
// Add AccountsModel.
|
// Add AccountsModel.
|
||||||
s.Accounts = NewAccountsModel(nil)
|
s.Accounts = NewAccountsModel(nil)
|
||||||
@ -325,41 +340,48 @@ func (s *FrontendQt) qtExecute(Procedure func(*FrontendQt) error) error {
|
|||||||
|
|
||||||
// Autostart.
|
// Autostart.
|
||||||
if s.Qml.IsFirstStart() {
|
if s.Qml.IsFirstStart() {
|
||||||
if s.AutostartEntry.IsEnabled() {
|
if s.autostart.IsEnabled() {
|
||||||
if err := s.AutostartEntry.Disable(); err != nil {
|
if err := s.autostart.Disable(); err != nil {
|
||||||
log.Error("First disable ", err)
|
log.Error("First disable ", err)
|
||||||
s.autostartError(err)
|
s.autostartError(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
s.toggleAutoStart()
|
s.toggleAutoStart()
|
||||||
}
|
}
|
||||||
if s.AutostartEntry.IsEnabled() {
|
if s.autostart.IsEnabled() {
|
||||||
s.Qml.SetIsAutoStart(true)
|
s.Qml.SetIsAutoStart(true)
|
||||||
} else {
|
} else {
|
||||||
s.Qml.SetIsAutoStart(false)
|
s.Qml.SetIsAutoStart(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.preferences.GetBool(preferences.AllowProxyKey) {
|
if s.settings.GetBool(settings.AutoUpdateKey) {
|
||||||
|
s.Qml.SetIsAutoUpdate(true)
|
||||||
|
} else {
|
||||||
|
s.Qml.SetIsAutoUpdate(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.settings.GetBool(settings.AllowProxyKey) {
|
||||||
s.Qml.SetIsProxyAllowed(true)
|
s.Qml.SetIsProxyAllowed(true)
|
||||||
} else {
|
} else {
|
||||||
s.Qml.SetIsProxyAllowed(false)
|
s.Qml.SetIsProxyAllowed(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Notify user about error during initialization.
|
if updater.UpdateChannel(s.settings.Get(settings.UpdateChannelKey)) == updater.EarlyChannel {
|
||||||
if s.notifyHasNoKeychain {
|
s.Qml.SetIsEarlyAccess(true)
|
||||||
s.Qml.NotifyHasNoKeychain()
|
} else {
|
||||||
|
s.Qml.SetIsEarlyAccess(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
s.eventListener.RetryEmit(events.TLSCertIssue)
|
|
||||||
s.eventListener.RetryEmit(events.ErrorEvent)
|
|
||||||
|
|
||||||
// Set reporting of outgoing email without encryption.
|
// Set reporting of outgoing email without encryption.
|
||||||
s.Qml.SetIsReportingOutgoingNoEnc(s.preferences.GetBool(preferences.ReportOutgoingNoEncKey))
|
s.Qml.SetIsReportingOutgoingNoEnc(s.settings.GetBool(settings.ReportOutgoingNoEncKey))
|
||||||
|
|
||||||
|
defaultIMAPPort, _ := strconv.Atoi(settings.DefaultIMAPPort)
|
||||||
|
defaultSMTPPort, _ := strconv.Atoi(settings.DefaultSMTPPort)
|
||||||
|
|
||||||
// IMAP/SMTP ports.
|
// IMAP/SMTP ports.
|
||||||
s.Qml.SetIsDefaultPort(
|
s.Qml.SetIsDefaultPort(
|
||||||
s.config.GetDefaultIMAPPort() == s.preferences.GetInt(preferences.IMAPPortKey) &&
|
defaultIMAPPort == s.settings.GetInt(settings.IMAPPortKey) &&
|
||||||
s.config.GetDefaultSMTPPort() == s.preferences.GetInt(preferences.SMTPPortKey),
|
defaultSMTPPort == s.settings.GetInt(settings.SMTPPortKey),
|
||||||
)
|
)
|
||||||
|
|
||||||
// Check QML is loaded properly.
|
// Check QML is loaded properly.
|
||||||
@ -376,6 +398,11 @@ func (s *FrontendQt) qtExecute(Procedure func(*FrontendQt) error) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer s.panicHandler.HandlePanic()
|
||||||
|
s.watchEvents()
|
||||||
|
}()
|
||||||
|
|
||||||
// Loop
|
// Loop
|
||||||
if ret := gui.QGuiApplication_Exec(); ret != 0 {
|
if ret := gui.QGuiApplication_Exec(); ret != 0 {
|
||||||
err := errors.New("Event loop ended with return value:" + string(ret))
|
err := errors.New("Event loop ended with return value:" + string(ret))
|
||||||
@ -387,48 +414,63 @@ func (s *FrontendQt) qtExecute(Procedure func(*FrontendQt) error) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *FrontendQt) openLogs() {
|
func (s *FrontendQt) openLogs() {
|
||||||
go open.Run(s.config.GetLogDir())
|
logsPath, err := s.locations.ProvideLogsPath()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
go open.Run(logsPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check version in separate goroutine to not block the GUI (avoid program not responding message).
|
func (s *FrontendQt) checkIsLatestVersionAndUpdate() bool {
|
||||||
func (s *FrontendQt) isNewVersionAvailable(showMessage bool) {
|
version, err := s.updater.Check()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
logrus.WithError(err).Error("An error occurred while checking updates manually")
|
||||||
|
s.Qml.NotifyManualUpdateError()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
s.SetVersion(version)
|
||||||
|
|
||||||
|
if !s.updater.IsUpdateApplicable(version) {
|
||||||
|
logrus.Debug("No need to update")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
logrus.WithField("version", version.Version).Info("An update is available")
|
||||||
|
|
||||||
|
if !s.updater.CanInstall(version) {
|
||||||
|
logrus.Debug("A manual update is required")
|
||||||
|
s.NotifyManualUpdate(version, false)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
s.NotifyManualUpdate(version, true)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *FrontendQt) checkAndOpenReleaseNotes() {
|
||||||
go func() {
|
go func() {
|
||||||
defer s.panicHandler.HandlePanic()
|
_ = s.checkIsLatestVersionAndUpdate()
|
||||||
defer s.Qml.ProcessFinished()
|
s.Qml.OpenReleaseNotesExternally()
|
||||||
isUpToDate, latestVersionInfo, err := s.updates.CheckIsUpToDate()
|
}()
|
||||||
if err != nil {
|
}
|
||||||
log.Warn("Can not retrieve version info: ", err)
|
|
||||||
s.checkInternet()
|
func (s *FrontendQt) checkForUpdates() {
|
||||||
return
|
go func() {
|
||||||
|
if s.checkIsLatestVersionAndUpdate() {
|
||||||
|
s.Qml.NotifyVersionIsTheLatest()
|
||||||
}
|
}
|
||||||
s.Qml.SetConnectionStatus(true) // If we are here connection is ok.
|
|
||||||
if isUpToDate {
|
|
||||||
s.Qml.SetUpdateState("upToDate")
|
|
||||||
if showMessage {
|
|
||||||
s.Qml.NotifyVersionIsTheLatest()
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
s.Qml.SetNewversion(latestVersionInfo.Version)
|
|
||||||
s.Qml.SetChangelog(latestVersionInfo.ReleaseNotes)
|
|
||||||
s.Qml.SetBugfixes(latestVersionInfo.ReleaseFixedBugs)
|
|
||||||
s.Qml.SetLandingPage(latestVersionInfo.LandingPage)
|
|
||||||
s.Qml.SetDownloadLink(latestVersionInfo.GetDownloadLink())
|
|
||||||
s.Qml.ShowWindow()
|
|
||||||
s.Qml.SetUpdateState("oldVersion")
|
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *FrontendQt) openLicenseFile() {
|
func (s *FrontendQt) openLicenseFile() {
|
||||||
go open.Run(s.config.GetLicenseFilePath())
|
go open.Run(s.locations.GetLicenseFilePath())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *FrontendQt) getLocalVersionInfo() {
|
func (s *FrontendQt) getLocalVersionInfo() {
|
||||||
defer s.Qml.ProcessFinished()
|
// NOTE: Fix this.
|
||||||
localVersion := s.updates.GetLocalVersion()
|
|
||||||
s.Qml.SetNewversion(localVersion.Version)
|
|
||||||
s.Qml.SetChangelog(localVersion.ReleaseNotes)
|
|
||||||
s.Qml.SetBugfixes(localVersion.ReleaseFixedBugs)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *FrontendQt) sendBug(description, client, address string) (isOK bool) {
|
func (s *FrontendQt) sendBug(description, client, address string) (isOK bool) {
|
||||||
@ -437,6 +479,9 @@ func (s *FrontendQt) sendBug(description, client, address string) (isOK bool) {
|
|||||||
if s.Accounts.Count() > 0 {
|
if s.Accounts.Count() > 0 {
|
||||||
accname = s.Accounts.get(0).Account()
|
accname = s.Accounts.get(0).Account()
|
||||||
}
|
}
|
||||||
|
if accname == "" {
|
||||||
|
accname = "Unknown account"
|
||||||
|
}
|
||||||
if err := s.bridge.ReportBug(
|
if err := s.bridge.ReportBug(
|
||||||
core.QSysInfo_ProductType(),
|
core.QSysInfo_ProductType(),
|
||||||
core.QSysInfo_PrettyProductName(),
|
core.QSysInfo_PrettyProductName(),
|
||||||
@ -465,18 +510,22 @@ func (s *FrontendQt) configureAppleMail(iAccount, iAddress int) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
imapPort := s.preferences.GetInt(preferences.IMAPPortKey)
|
imapPort := s.settings.GetInt(settings.IMAPPortKey)
|
||||||
imapSSL := false
|
imapSSL := false
|
||||||
smtpPort := s.preferences.GetInt(preferences.SMTPPortKey)
|
smtpPort := s.settings.GetInt(settings.SMTPPortKey)
|
||||||
smtpSSL := s.preferences.GetBool(preferences.SMTPSSLKey)
|
smtpSSL := s.settings.GetBool(settings.SMTPSSLKey)
|
||||||
|
|
||||||
// If configuring apple mail for Catalina or newer, users should use SSL.
|
// If configuring apple mail for Catalina or newer, users should use SSL.
|
||||||
doRestart := false
|
doRestart := false
|
||||||
if !smtpSSL && useragent.IsCatalinaOrNewer() {
|
if !smtpSSL && useragent.IsCatalinaOrNewer() {
|
||||||
smtpSSL = true
|
smtpSSL = true
|
||||||
s.preferences.SetBool(preferences.SMTPSSLKey, true)
|
s.settings.SetBool(settings.SMTPSSLKey, true)
|
||||||
log.Warn("Detected Catalina or newer with bad SMTP SSL settings, now using SSL, bridge needs to restart")
|
log.Warn("Detected Catalina or newer with bad SMTP SSL settings, now using SSL, bridge needs to restart")
|
||||||
doRestart = true
|
doRestart = true
|
||||||
|
} else if smtpSSL {
|
||||||
|
log.Debug("Bridge is already using SMTP SSL, no need to restart")
|
||||||
|
} else {
|
||||||
|
log.Debug("OS is pre-catalina (or not darwin at all), no need to change to SMTP SSL")
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, autoConf := range autoconfig.Available() {
|
for _, autoConf := range autoconfig.Available() {
|
||||||
@ -489,7 +538,7 @@ func (s *FrontendQt) configureAppleMail(iAccount, iAddress int) {
|
|||||||
|
|
||||||
if doRestart {
|
if doRestart {
|
||||||
time.Sleep(2 * time.Second)
|
time.Sleep(2 * time.Second)
|
||||||
s.Qml.SetIsRestarting(true)
|
s.restarter.SetToRestart()
|
||||||
s.App.Quit()
|
s.App.Quit()
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
@ -498,42 +547,74 @@ func (s *FrontendQt) configureAppleMail(iAccount, iAddress int) {
|
|||||||
func (s *FrontendQt) toggleAutoStart() {
|
func (s *FrontendQt) toggleAutoStart() {
|
||||||
defer s.Qml.ProcessFinished()
|
defer s.Qml.ProcessFinished()
|
||||||
var err error
|
var err error
|
||||||
if s.AutostartEntry.IsEnabled() {
|
if s.autostart.IsEnabled() {
|
||||||
err = s.AutostartEntry.Disable()
|
err = s.autostart.Disable()
|
||||||
} else {
|
} else {
|
||||||
err = s.AutostartEntry.Enable()
|
err = s.autostart.Enable()
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Enable autostart: ", err)
|
log.Error("Enable autostart: ", err)
|
||||||
s.autostartError(err)
|
s.autostartError(err)
|
||||||
}
|
}
|
||||||
if s.AutostartEntry.IsEnabled() {
|
if s.autostart.IsEnabled() {
|
||||||
s.Qml.SetIsAutoStart(true)
|
s.Qml.SetIsAutoStart(true)
|
||||||
} else {
|
} else {
|
||||||
s.Qml.SetIsAutoStart(false)
|
s.Qml.SetIsAutoStart(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *FrontendQt) toggleAutoUpdate() {
|
||||||
|
defer s.Qml.ProcessFinished()
|
||||||
|
|
||||||
|
if s.settings.GetBool(settings.AutoUpdateKey) {
|
||||||
|
s.settings.SetBool(settings.AutoUpdateKey, false)
|
||||||
|
s.Qml.SetIsAutoUpdate(false)
|
||||||
|
} else {
|
||||||
|
s.settings.SetBool(settings.AutoUpdateKey, true)
|
||||||
|
s.Qml.SetIsAutoUpdate(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *FrontendQt) toggleEarlyAccess() {
|
||||||
|
defer s.Qml.ProcessFinished()
|
||||||
|
|
||||||
|
channel := s.bridge.GetUpdateChannel()
|
||||||
|
if channel == updater.EarlyChannel {
|
||||||
|
channel = updater.StableChannel
|
||||||
|
} else {
|
||||||
|
channel = updater.EarlyChannel
|
||||||
|
}
|
||||||
|
|
||||||
|
err := s.bridge.SetUpdateChannel(channel)
|
||||||
|
s.Qml.SetIsEarlyAccess(channel == updater.EarlyChannel)
|
||||||
|
if err != nil {
|
||||||
|
s.Qml.NotifyManualUpdateError()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.restarter.SetToRestart()
|
||||||
|
s.App.Quit()
|
||||||
|
}
|
||||||
|
|
||||||
func (s *FrontendQt) toggleAllowProxy() {
|
func (s *FrontendQt) toggleAllowProxy() {
|
||||||
defer s.Qml.ProcessFinished()
|
defer s.Qml.ProcessFinished()
|
||||||
|
|
||||||
if s.preferences.GetBool(preferences.AllowProxyKey) {
|
if s.settings.GetBool(settings.AllowProxyKey) {
|
||||||
s.preferences.SetBool(preferences.AllowProxyKey, false)
|
s.settings.SetBool(settings.AllowProxyKey, false)
|
||||||
s.bridge.DisallowProxy()
|
s.bridge.DisallowProxy()
|
||||||
s.Qml.SetIsProxyAllowed(false)
|
s.Qml.SetIsProxyAllowed(false)
|
||||||
} else {
|
} else {
|
||||||
s.preferences.SetBool(preferences.AllowProxyKey, true)
|
s.settings.SetBool(settings.AllowProxyKey, true)
|
||||||
s.bridge.AllowProxy()
|
s.bridge.AllowProxy()
|
||||||
s.Qml.SetIsProxyAllowed(true)
|
s.Qml.SetIsProxyAllowed(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *FrontendQt) getIMAPPort() string {
|
func (s *FrontendQt) getIMAPPort() string {
|
||||||
return s.preferences.Get(preferences.IMAPPortKey)
|
return s.settings.Get(settings.IMAPPortKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *FrontendQt) getSMTPPort() string {
|
func (s *FrontendQt) getSMTPPort() string {
|
||||||
return s.preferences.Get(preferences.SMTPPortKey)
|
return s.settings.Get(settings.SMTPPortKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return 0 -- port is free to use for server.
|
// Return 0 -- port is free to use for server.
|
||||||
@ -550,13 +631,13 @@ func (s *FrontendQt) isPortOpen(portStr string) int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *FrontendQt) setPortsAndSecurity(imapPort, smtpPort string, useSTARTTLSforSMTP bool) {
|
func (s *FrontendQt) setPortsAndSecurity(imapPort, smtpPort string, useSTARTTLSforSMTP bool) {
|
||||||
s.preferences.Set(preferences.IMAPPortKey, imapPort)
|
s.settings.Set(settings.IMAPPortKey, imapPort)
|
||||||
s.preferences.Set(preferences.SMTPPortKey, smtpPort)
|
s.settings.Set(settings.SMTPPortKey, smtpPort)
|
||||||
s.preferences.SetBool(preferences.SMTPSSLKey, !useSTARTTLSforSMTP)
|
s.settings.SetBool(settings.SMTPSSLKey, !useSTARTTLSforSMTP)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *FrontendQt) isSMTPSTARTTLS() bool {
|
func (s *FrontendQt) isSMTPSTARTTLS() bool {
|
||||||
return !s.preferences.GetBool(preferences.SMTPSSLKey)
|
return !s.settings.GetBool(settings.SMTPSSLKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *FrontendQt) checkInternet() {
|
func (s *FrontendQt) checkInternet() {
|
||||||
@ -594,7 +675,7 @@ func (s *FrontendQt) autostartError(err error) {
|
|||||||
|
|
||||||
func (s *FrontendQt) toggleIsReportingOutgoingNoEnc() {
|
func (s *FrontendQt) toggleIsReportingOutgoingNoEnc() {
|
||||||
shouldReport := !s.Qml.IsReportingOutgoingNoEnc()
|
shouldReport := !s.Qml.IsReportingOutgoingNoEnc()
|
||||||
s.preferences.SetBool(preferences.ReportOutgoingNoEncKey, shouldReport)
|
s.settings.SetBool(settings.ReportOutgoingNoEncKey, shouldReport)
|
||||||
s.Qml.SetIsReportingOutgoingNoEnc(shouldReport)
|
s.Qml.SetIsReportingOutgoingNoEnc(shouldReport)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -607,31 +688,26 @@ func (s *FrontendQt) saveOutgoingNoEncPopupCoord(x, y float32) {
|
|||||||
//prefs.SetFloat(prefs.OutgoingNoEncPopupCoordY, y)
|
//prefs.SetFloat(prefs.OutgoingNoEncPopupCoordY, y)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *FrontendQt) StartUpdate() {
|
func (s *FrontendQt) startManualUpdate() {
|
||||||
progress := make(chan updates.Progress)
|
go func() {
|
||||||
go func() { // Update progress in QML.
|
err := s.updater.InstallUpdate(s.updateInfo)
|
||||||
defer s.panicHandler.HandlePanic()
|
|
||||||
for current := range progress {
|
if err != nil {
|
||||||
s.Qml.SetProgress(current.Processed)
|
logrus.WithError(err).Error("An error occurred while installing updates manually")
|
||||||
s.Qml.SetProgressDescription(strconv.Itoa(current.Description))
|
s.Qml.NotifyManualUpdateError()
|
||||||
// Error happend
|
} else {
|
||||||
if current.Err != nil {
|
s.Qml.NotifyManualUpdateRestartNeeded()
|
||||||
log.Error("update progress: ", current.Err)
|
|
||||||
s.Qml.UpdateFinished(true)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Finished everything OK.
|
|
||||||
if current.Description >= updates.InfoQuitApp {
|
|
||||||
s.Qml.UpdateFinished(false)
|
|
||||||
time.Sleep(3 * time.Second) // Just notify.
|
|
||||||
s.Qml.SetIsRestarting(current.Description == updates.InfoRestartApp)
|
|
||||||
s.App.Quit()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
go func() {
|
}
|
||||||
defer s.panicHandler.HandlePanic()
|
|
||||||
s.updates.StartUpgrade(progress)
|
func (s *FrontendQt) WaitUntilFrontendIsReady() {
|
||||||
}()
|
s.initializing.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
// setGUIIsReady unlocks the WaitFrontendIsReady.
|
||||||
|
func (s *FrontendQt) setGUIIsReady() {
|
||||||
|
s.initializationDone.Do(func() {
|
||||||
|
s.initializing.Done()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -23,8 +23,11 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/ProtonMail/go-autostart"
|
||||||
|
"github.com/ProtonMail/proton-bridge/internal/config/settings"
|
||||||
"github.com/ProtonMail/proton-bridge/internal/frontend/types"
|
"github.com/ProtonMail/proton-bridge/internal/frontend/types"
|
||||||
"github.com/ProtonMail/proton-bridge/pkg/config"
|
"github.com/ProtonMail/proton-bridge/internal/locations"
|
||||||
|
"github.com/ProtonMail/proton-bridge/internal/updater"
|
||||||
"github.com/ProtonMail/proton-bridge/pkg/listener"
|
"github.com/ProtonMail/proton-bridge/pkg/listener"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
@ -33,7 +36,7 @@ var log = logrus.WithField("pkg", "frontend-nogui") //nolint[gochecknoglobals]
|
|||||||
|
|
||||||
type FrontendHeadless struct{}
|
type FrontendHeadless struct{}
|
||||||
|
|
||||||
func (s *FrontendHeadless) Loop(credentialsError error) error {
|
func (s *FrontendHeadless) Loop() error {
|
||||||
log.Info("Check status on localhost:8081")
|
log.Info("Check status on localhost:8081")
|
||||||
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
fmt.Fprintf(w, "Bridge is running")
|
fmt.Fprintf(w, "Bridge is running")
|
||||||
@ -41,20 +44,37 @@ func (s *FrontendHeadless) Loop(credentialsError error) error {
|
|||||||
return http.ListenAndServe(":8081", nil)
|
return http.ListenAndServe(":8081", nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *FrontendHeadless) InstanceExistAlert() {}
|
func (s *FrontendHeadless) NotifyManualUpdate(update updater.VersionInfo, canInstall bool) {
|
||||||
func (s *FrontendHeadless) IsAppRestarting() bool { return false }
|
// NOTE: Save the update somewhere so that it can be installed when user chooses "install now".
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *FrontendHeadless) WaitUntilFrontendIsReady() {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *FrontendHeadless) SetVersion(update updater.VersionInfo) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *FrontendHeadless) NotifySilentUpdateInstalled() {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *FrontendHeadless) NotifySilentUpdateError(err error) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *FrontendHeadless) InstanceExistAlert() {}
|
||||||
|
|
||||||
func New(
|
func New(
|
||||||
version,
|
version,
|
||||||
buildVersion string,
|
buildVersion, appName string,
|
||||||
showWindowOnStart bool,
|
showWindowOnStart bool,
|
||||||
panicHandler types.PanicHandler,
|
panicHandler types.PanicHandler,
|
||||||
config *config.Config,
|
locations *locations.Locations,
|
||||||
preferences *config.Preferences,
|
settings *settings.Settings,
|
||||||
eventListener listener.Listener,
|
eventListener listener.Listener,
|
||||||
updates types.Updater,
|
updater types.Updater,
|
||||||
bridge types.Bridger,
|
bridge types.Bridger,
|
||||||
noEncConfirmator types.NoEncConfirmator,
|
noEncConfirmator types.NoEncConfirmator,
|
||||||
|
autostart *autostart.App,
|
||||||
|
restarter types.Restarter,
|
||||||
) *FrontendHeadless {
|
) *FrontendHeadless {
|
||||||
return &FrontendHeadless{}
|
return &FrontendHeadless{}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -653,17 +653,4 @@
|
|||||||
<translation type="unfinished"></translation>
|
<translation type="unfinished"></translation>
|
||||||
</message>
|
</message>
|
||||||
</context>
|
</context>
|
||||||
<context>
|
|
||||||
<name>VersionInfo</name>
|
|
||||||
<message>
|
|
||||||
<location filename="qml/BridgeUI/VersionInfo.qml" line="30"/>
|
|
||||||
<source>Release notes:</source>
|
|
||||||
<translation type="unfinished"></translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
|
||||||
<location filename="qml/BridgeUI/VersionInfo.qml" line="53"/>
|
|
||||||
<source>Fixed bugs:</source>
|
|
||||||
<translation type="unfinished"></translation>
|
|
||||||
</message>
|
|
||||||
</context>
|
|
||||||
</TS>
|
</TS>
|
||||||
|
|||||||
@ -25,7 +25,7 @@ import (
|
|||||||
"github.com/therecipe/qt/core"
|
"github.com/therecipe/qt/core"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Interface between go and qml.
|
// GoQMLInterface between go and qml.
|
||||||
//
|
//
|
||||||
// Here we implement all the signals / methods.
|
// Here we implement all the signals / methods.
|
||||||
type GoQMLInterface struct {
|
type GoQMLInterface struct {
|
||||||
@ -34,6 +34,8 @@ type GoQMLInterface struct {
|
|||||||
_ func() `constructor:"init"`
|
_ func() `constructor:"init"`
|
||||||
|
|
||||||
_ bool `property:"isAutoStart"`
|
_ bool `property:"isAutoStart"`
|
||||||
|
_ bool `property:"isAutoUpdate"`
|
||||||
|
_ bool `property:"isEarlyAccess"`
|
||||||
_ bool `property:"isProxyAllowed"`
|
_ bool `property:"isProxyAllowed"`
|
||||||
_ string `property:"currentAddress"`
|
_ string `property:"currentAddress"`
|
||||||
_ string `property:"goos"`
|
_ string `property:"goos"`
|
||||||
@ -41,17 +43,28 @@ type GoQMLInterface struct {
|
|||||||
_ bool `property:"isShownOnStart"`
|
_ bool `property:"isShownOnStart"`
|
||||||
_ bool `property:"isFirstStart"`
|
_ bool `property:"isFirstStart"`
|
||||||
_ bool `property:"isFreshVersion"`
|
_ bool `property:"isFreshVersion"`
|
||||||
_ bool `property:"isRestarting"`
|
|
||||||
_ bool `property:"isConnectionOK"`
|
_ bool `property:"isConnectionOK"`
|
||||||
_ bool `property:"isDefaultPort"`
|
_ bool `property:"isDefaultPort"`
|
||||||
|
|
||||||
_ string `property:"programTitle"`
|
_ string `property:"programTitle"`
|
||||||
_ string `property:"newversion"`
|
|
||||||
_ string `property:"fullversion"`
|
_ string `property:"fullversion"`
|
||||||
_ string `property:"downloadLink"`
|
_ string `property:"downloadLink"`
|
||||||
_ string `property:"landingPage"`
|
|
||||||
_ string `property:"changelog"`
|
_ string `property:"updateVersion"`
|
||||||
_ string `property:"bugfixes"`
|
_ bool `property:"updateCanInstall"`
|
||||||
|
_ string `property:"updateLandingPage"`
|
||||||
|
_ string `property:"updateReleaseNotesLink"`
|
||||||
|
_ func() `signal:"notifyManualUpdate"`
|
||||||
|
_ func() `signal:"notifyManualUpdateRestartNeeded"`
|
||||||
|
_ func() `signal:"notifyManualUpdateError"`
|
||||||
|
_ func() `signal:"notifyForceUpdate"`
|
||||||
|
_ func() `signal:"notifySilentUpdateRestartNeeded"`
|
||||||
|
_ func() `signal:"notifySilentUpdateError"`
|
||||||
|
_ func() `slot:"checkForUpdates"`
|
||||||
|
_ func() `slot:"checkAndOpenReleaseNotes"`
|
||||||
|
_ func() `signal:"openReleaseNotesExternally"`
|
||||||
|
_ func() `slot:"startManualUpdate"`
|
||||||
|
_ func() `slot:"guiIsReady"`
|
||||||
|
|
||||||
// Translations.
|
// Translations.
|
||||||
_ string `property:"wrongCredentials"`
|
_ string `property:"wrongCredentials"`
|
||||||
@ -70,6 +83,8 @@ type GoQMLInterface struct {
|
|||||||
_ func(updateState string) `signal:"setUpdateState"`
|
_ func(updateState string) `signal:"setUpdateState"`
|
||||||
_ func() `slot:"checkInternet"`
|
_ func() `slot:"checkInternet"`
|
||||||
|
|
||||||
|
_ func() `slot:"setToRestart"`
|
||||||
|
|
||||||
_ func(systX, systY, systW, systH int) `signal:"toggleMainWin"`
|
_ func(systX, systY, systW, systH int) `signal:"toggleMainWin"`
|
||||||
|
|
||||||
_ func() `signal:"processFinished"`
|
_ func() `signal:"processFinished"`
|
||||||
@ -82,6 +97,8 @@ type GoQMLInterface struct {
|
|||||||
_ func() `signal:"showQuit"`
|
_ func() `signal:"showQuit"`
|
||||||
|
|
||||||
_ func() `slot:"toggleAutoStart"`
|
_ func() `slot:"toggleAutoStart"`
|
||||||
|
_ func() `slot:"toggleAutoUpdate"`
|
||||||
|
_ func() `slot:"toggleEarlyAccess"`
|
||||||
_ func() `slot:"toggleAllowProxy"`
|
_ func() `slot:"toggleAllowProxy"`
|
||||||
_ func() `slot:"loadAccounts"`
|
_ func() `slot:"loadAccounts"`
|
||||||
_ func() `slot:"openLogs"`
|
_ func() `slot:"openLogs"`
|
||||||
@ -121,7 +138,6 @@ type GoQMLInterface struct {
|
|||||||
_ func() `signal:"notifyVersionIsTheLatest"`
|
_ func() `signal:"notifyVersionIsTheLatest"`
|
||||||
_ func() `signal:"notifyKeychainRebuild"`
|
_ func() `signal:"notifyKeychainRebuild"`
|
||||||
_ func() `signal:"notifyHasNoKeychain"`
|
_ func() `signal:"notifyHasNoKeychain"`
|
||||||
_ func() `signal:"notifyUpdate"`
|
|
||||||
_ func(accname string) `signal:"notifyLogout"`
|
_ func(accname string) `signal:"notifyLogout"`
|
||||||
_ func(accname string) `signal:"notifyAddressChanged"`
|
_ func(accname string) `signal:"notifyAddressChanged"`
|
||||||
_ func(accname string) `signal:"notifyAddressChangedLogout"`
|
_ func(accname string) `signal:"notifyAddressChangedLogout"`
|
||||||
@ -137,7 +153,6 @@ type GoQMLInterface struct {
|
|||||||
_ func(recipient string) `signal:"showNoActiveKeyForRecipient"`
|
_ func(recipient string) `signal:"showNoActiveKeyForRecipient"`
|
||||||
_ func() `signal:"showCertIssue"`
|
_ func() `signal:"showCertIssue"`
|
||||||
|
|
||||||
_ func() `slot:"startUpdate"`
|
|
||||||
_ func(hasError bool) `signal:"updateFinished"`
|
_ func(hasError bool) `signal:"updateFinished"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -147,15 +162,18 @@ func (s *GoQMLInterface) init() {}
|
|||||||
// SetFrontend connects all slots and signals from Go to QML.
|
// SetFrontend connects all slots and signals from Go to QML.
|
||||||
func (s *GoQMLInterface) SetFrontend(f *FrontendQt) {
|
func (s *GoQMLInterface) SetFrontend(f *FrontendQt) {
|
||||||
s.ConnectToggleAutoStart(f.toggleAutoStart)
|
s.ConnectToggleAutoStart(f.toggleAutoStart)
|
||||||
|
s.ConnectToggleEarlyAccess(f.toggleEarlyAccess)
|
||||||
|
s.ConnectToggleAutoUpdate(f.toggleAutoUpdate)
|
||||||
s.ConnectToggleAllowProxy(f.toggleAllowProxy)
|
s.ConnectToggleAllowProxy(f.toggleAllowProxy)
|
||||||
s.ConnectLoadAccounts(f.loadAccounts)
|
s.ConnectLoadAccounts(f.loadAccounts)
|
||||||
s.ConnectOpenLogs(f.openLogs)
|
s.ConnectOpenLogs(f.openLogs)
|
||||||
s.ConnectClearCache(f.clearCache)
|
s.ConnectClearCache(f.clearCache)
|
||||||
s.ConnectClearKeychain(f.clearKeychain)
|
s.ConnectClearKeychain(f.clearKeychain)
|
||||||
|
|
||||||
s.ConnectOpenLicenseFile(f.openLicenseFile)
|
s.ConnectOpenLicenseFile(f.openLicenseFile)
|
||||||
|
s.ConnectStartManualUpdate(f.startManualUpdate)
|
||||||
|
s.ConnectGuiIsReady(f.setGUIIsReady)
|
||||||
s.ConnectGetLocalVersionInfo(f.getLocalVersionInfo)
|
s.ConnectGetLocalVersionInfo(f.getLocalVersionInfo)
|
||||||
s.ConnectIsNewVersionAvailable(f.isNewVersionAvailable)
|
s.ConnectCheckForUpdates(f.checkForUpdates)
|
||||||
s.ConnectGetIMAPPort(f.getIMAPPort)
|
s.ConnectGetIMAPPort(f.getIMAPPort)
|
||||||
s.ConnectGetSMTPPort(f.getSMTPPort)
|
s.ConnectGetSMTPPort(f.getSMTPPort)
|
||||||
s.ConnectGetLastMailClient(f.getLastMailClient)
|
s.ConnectGetLastMailClient(f.getLastMailClient)
|
||||||
@ -178,7 +196,6 @@ func (s *GoQMLInterface) SetFrontend(f *FrontendQt) {
|
|||||||
s.ConnectSwitchAddressMode(f.switchAddressModeUser)
|
s.ConnectSwitchAddressMode(f.switchAddressModeUser)
|
||||||
|
|
||||||
s.SetGoos(runtime.GOOS)
|
s.SetGoos(runtime.GOOS)
|
||||||
s.SetIsRestarting(false)
|
|
||||||
s.SetProgramTitle(f.programName)
|
s.SetProgramTitle(f.programName)
|
||||||
|
|
||||||
s.ConnectGetBackendVersion(func() string {
|
s.ConnectGetBackendVersion(func() string {
|
||||||
@ -187,8 +204,9 @@ func (s *GoQMLInterface) SetFrontend(f *FrontendQt) {
|
|||||||
|
|
||||||
s.ConnectCheckInternet(f.checkInternet)
|
s.ConnectCheckInternet(f.checkInternet)
|
||||||
|
|
||||||
|
s.ConnectSetToRestart(f.restarter.SetToRestart)
|
||||||
|
|
||||||
s.ConnectToggleIsReportingOutgoingNoEnc(f.toggleIsReportingOutgoingNoEnc)
|
s.ConnectToggleIsReportingOutgoingNoEnc(f.toggleIsReportingOutgoingNoEnc)
|
||||||
s.ConnectShouldSendAnswer(f.shouldSendAnswer)
|
s.ConnectShouldSendAnswer(f.shouldSendAnswer)
|
||||||
s.ConnectSaveOutgoingNoEncPopupCoord(f.saveOutgoingNoEncPopupCoord)
|
s.ConnectSaveOutgoingNoEncPopupCoord(f.saveOutgoingNoEncPopupCoord)
|
||||||
s.ConnectStartUpdate(f.StartUpdate)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -71,7 +71,6 @@
|
|||||||
<file alias="OutgoingNoEncPopup.qml" >./qml/BridgeUI/OutgoingNoEncPopup.qml</file>
|
<file alias="OutgoingNoEncPopup.qml" >./qml/BridgeUI/OutgoingNoEncPopup.qml</file>
|
||||||
<file alias="SettingsView.qml" >./qml/BridgeUI/SettingsView.qml</file>
|
<file alias="SettingsView.qml" >./qml/BridgeUI/SettingsView.qml</file>
|
||||||
<file alias="StatusFooter.qml" >./qml/BridgeUI/StatusFooter.qml</file>
|
<file alias="StatusFooter.qml" >./qml/BridgeUI/StatusFooter.qml</file>
|
||||||
<file alias="VersionInfo.qml" >./qml/BridgeUI/VersionInfo.qml</file>
|
|
||||||
</qresource>
|
</qresource>
|
||||||
<qresource prefix="ImportExportUI">
|
<qresource prefix="ImportExportUI">
|
||||||
<file alias="qmldir" >./qml/ImportExportUI/qmldir</file>
|
<file alias="qmldir" >./qml/ImportExportUI/qmldir</file>
|
||||||
@ -104,7 +103,6 @@
|
|||||||
<file alias="SelectFolderMenu.qml" >./qml/ImportExportUI/SelectFolderMenu.qml</file>
|
<file alias="SelectFolderMenu.qml" >./qml/ImportExportUI/SelectFolderMenu.qml</file>
|
||||||
<file alias="SelectLabelsMenu.qml" >./qml/ImportExportUI/SelectLabelsMenu.qml</file>
|
<file alias="SelectLabelsMenu.qml" >./qml/ImportExportUI/SelectLabelsMenu.qml</file>
|
||||||
<file alias="SettingsView.qml" >./qml/ImportExportUI/SettingsView.qml</file>
|
<file alias="SettingsView.qml" >./qml/ImportExportUI/SettingsView.qml</file>
|
||||||
<file alias="VersionInfo.qml" >./qml/ImportExportUI/VersionInfo.qml</file>
|
|
||||||
<file alias="images/folder_open.png" >./share/icons/folder_open.png</file>
|
<file alias="images/folder_open.png" >./share/icons/folder_open.png</file>
|
||||||
<file alias="images/envelope_open.png" >./share/icons/envelope_open.png</file>
|
<file alias="images/envelope_open.png" >./share/icons/envelope_open.png</file>
|
||||||
</qresource>
|
</qresource>
|
||||||
|
|||||||
@ -22,7 +22,7 @@ import (
|
|||||||
"github.com/ProtonMail/proton-bridge/internal/bridge"
|
"github.com/ProtonMail/proton-bridge/internal/bridge"
|
||||||
"github.com/ProtonMail/proton-bridge/internal/importexport"
|
"github.com/ProtonMail/proton-bridge/internal/importexport"
|
||||||
"github.com/ProtonMail/proton-bridge/internal/transfer"
|
"github.com/ProtonMail/proton-bridge/internal/transfer"
|
||||||
"github.com/ProtonMail/proton-bridge/internal/updates"
|
"github.com/ProtonMail/proton-bridge/internal/updater"
|
||||||
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -31,18 +31,22 @@ type PanicHandler interface {
|
|||||||
HandlePanic()
|
HandlePanic()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Updater is an interface for handling Bridge upgrades.
|
// Restarter allows the app to set itself to restart next time it is closed.
|
||||||
type Updater interface {
|
type Restarter interface {
|
||||||
CheckIsUpToDate() (isUpToDate bool, latestVersion updates.VersionInfo, err error)
|
SetToRestart()
|
||||||
GetDownloadLink() string
|
|
||||||
GetLocalVersion() updates.VersionInfo
|
|
||||||
StartUpgrade(currentStatus chan<- updates.Progress)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type NoEncConfirmator interface {
|
type NoEncConfirmator interface {
|
||||||
ConfirmNoEncryption(string, bool)
|
ConfirmNoEncryption(string, bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Updater interface {
|
||||||
|
Check() (updater.VersionInfo, error)
|
||||||
|
InstallUpdate(updater.VersionInfo) error
|
||||||
|
IsUpdateApplicable(updater.VersionInfo) bool
|
||||||
|
CanInstall(updater.VersionInfo) bool
|
||||||
|
}
|
||||||
|
|
||||||
// UserManager is an interface of users needed by frontend.
|
// UserManager is an interface of users needed by frontend.
|
||||||
type UserManager interface {
|
type UserManager interface {
|
||||||
Login(username, password string) (pmapi.Client, *pmapi.Auth, error)
|
Login(username, password string) (pmapi.Client, *pmapi.Auth, error)
|
||||||
@ -76,6 +80,8 @@ type Bridger interface {
|
|||||||
ReportBug(osType, osVersion, description, accountName, address, emailClient string) error
|
ReportBug(osType, osVersion, description, accountName, address, emailClient string) error
|
||||||
AllowProxy()
|
AllowProxy()
|
||||||
DisallowProxy()
|
DisallowProxy()
|
||||||
|
GetUpdateChannel() updater.UpdateChannel
|
||||||
|
SetUpdateChannel(updater.UpdateChannel) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type bridgeWrap struct {
|
type bridgeWrap struct {
|
||||||
|
|||||||
@ -37,7 +37,7 @@ type panicHandler interface {
|
|||||||
type imapBackend struct {
|
type imapBackend struct {
|
||||||
panicHandler panicHandler
|
panicHandler panicHandler
|
||||||
bridge bridger
|
bridge bridger
|
||||||
updates chan goIMAPBackend.Update
|
updates *imapUpdates
|
||||||
eventListener listener.Listener
|
eventListener listener.Listener
|
||||||
|
|
||||||
users map[string]*imapUser
|
users map[string]*imapUser
|
||||||
@ -46,20 +46,17 @@ type imapBackend struct {
|
|||||||
imapCache map[string]map[string]string
|
imapCache map[string]map[string]string
|
||||||
imapCachePath string
|
imapCachePath string
|
||||||
imapCacheLock *sync.RWMutex
|
imapCacheLock *sync.RWMutex
|
||||||
|
|
||||||
updatesBlocking map[string]bool
|
|
||||||
updatesBlockingLocker sync.Locker
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewIMAPBackend returns struct implementing go-imap/backend interface.
|
// NewIMAPBackend returns struct implementing go-imap/backend interface.
|
||||||
func NewIMAPBackend(
|
func NewIMAPBackend(
|
||||||
panicHandler panicHandler,
|
panicHandler panicHandler,
|
||||||
eventListener listener.Listener,
|
eventListener listener.Listener,
|
||||||
cfg configProvider,
|
cache cacheProvider,
|
||||||
bridge *bridge.Bridge,
|
bridge *bridge.Bridge,
|
||||||
) *imapBackend { //nolint[golint]
|
) *imapBackend { //nolint[golint]
|
||||||
bridgeWrap := newBridgeWrap(bridge)
|
bridgeWrap := newBridgeWrap(bridge)
|
||||||
backend := newIMAPBackend(panicHandler, cfg, bridgeWrap, eventListener)
|
backend := newIMAPBackend(panicHandler, cache, bridgeWrap, eventListener)
|
||||||
|
|
||||||
go backend.monitorDisconnectedUsers()
|
go backend.monitorDisconnectedUsers()
|
||||||
|
|
||||||
@ -68,24 +65,21 @@ func NewIMAPBackend(
|
|||||||
|
|
||||||
func newIMAPBackend(
|
func newIMAPBackend(
|
||||||
panicHandler panicHandler,
|
panicHandler panicHandler,
|
||||||
cfg configProvider,
|
cache cacheProvider,
|
||||||
bridge bridger,
|
bridge bridger,
|
||||||
eventListener listener.Listener,
|
eventListener listener.Listener,
|
||||||
) *imapBackend {
|
) *imapBackend {
|
||||||
return &imapBackend{
|
return &imapBackend{
|
||||||
panicHandler: panicHandler,
|
panicHandler: panicHandler,
|
||||||
bridge: bridge,
|
bridge: bridge,
|
||||||
updates: make(chan goIMAPBackend.Update),
|
updates: newIMAPUpdates(),
|
||||||
eventListener: eventListener,
|
eventListener: eventListener,
|
||||||
|
|
||||||
users: map[string]*imapUser{},
|
users: map[string]*imapUser{},
|
||||||
usersLocker: &sync.Mutex{},
|
usersLocker: &sync.Mutex{},
|
||||||
|
|
||||||
imapCachePath: cfg.GetIMAPCachePath(),
|
imapCachePath: cache.GetIMAPCachePath(),
|
||||||
imapCacheLock: &sync.RWMutex{},
|
imapCacheLock: &sync.RWMutex{},
|
||||||
|
|
||||||
updatesBlocking: map[string]bool{},
|
|
||||||
updatesBlockingLocker: &sync.Mutex{},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -172,7 +166,7 @@ func (ib *imapBackend) Login(_ *imap.ConnInfo, username, password string) (goIMA
|
|||||||
// so that it doesn't make bridge slow for users who are only using bridge for SMTP
|
// so that it doesn't make bridge slow for users who are only using bridge for SMTP
|
||||||
// (otherwise the store will be locked for 1 sec per email during synchronization).
|
// (otherwise the store will be locked for 1 sec per email during synchronization).
|
||||||
if store := imapUser.user.GetStore(); store != nil {
|
if store := imapUser.user.GetStore(); store != nil {
|
||||||
store.SetChangeNotifier(ib)
|
store.SetChangeNotifier(ib.updates)
|
||||||
}
|
}
|
||||||
|
|
||||||
return imapUser, nil
|
return imapUser, nil
|
||||||
@ -183,7 +177,7 @@ func (ib *imapBackend) Updates() <-chan goIMAPBackend.Update {
|
|||||||
// Called from go-imap in goroutines - we need to handle panics for each function.
|
// Called from go-imap in goroutines - we need to handle panics for each function.
|
||||||
defer ib.panicHandler.HandlePanic()
|
defer ib.panicHandler.HandlePanic()
|
||||||
|
|
||||||
return ib.updates
|
return ib.updates.ch
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ib *imapBackend) CreateMessageLimit() *uint32 {
|
func (ib *imapBackend) CreateMessageLimit() *uint32 {
|
||||||
|
|||||||
@ -23,8 +23,7 @@ import (
|
|||||||
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
||||||
)
|
)
|
||||||
|
|
||||||
type configProvider interface {
|
type cacheProvider interface {
|
||||||
GetEventsPath() string
|
|
||||||
GetDBDir() string
|
GetDBDir() string
|
||||||
GetIMAPCachePath() string
|
GetIMAPCachePath() string
|
||||||
}
|
}
|
||||||
|
|||||||
8
internal/imap/cache/cache.go
vendored
8
internal/imap/cache/cache.go
vendored
@ -23,7 +23,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
backendMessage "github.com/ProtonMail/proton-bridge/pkg/message"
|
pkgMsg "github.com/ProtonMail/proton-bridge/pkg/message"
|
||||||
)
|
)
|
||||||
|
|
||||||
type key struct {
|
type key struct {
|
||||||
@ -41,7 +41,7 @@ func (s oldestFirst) Less(i, j int) bool { return s[i].Timestamp < s[j].Timestam
|
|||||||
type cachedMessage struct {
|
type cachedMessage struct {
|
||||||
key
|
key
|
||||||
data []byte
|
data []byte
|
||||||
structure backendMessage.BodyStructure
|
structure pkgMsg.BodyStructure
|
||||||
}
|
}
|
||||||
|
|
||||||
//nolint[gochecknoglobals]
|
//nolint[gochecknoglobals]
|
||||||
@ -101,7 +101,7 @@ func BuildUnlock(messageID string) {
|
|||||||
delete(buildLocks, messageID)
|
delete(buildLocks, messageID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func LoadMail(mID string) (reader *bytes.Reader, structure *backendMessage.BodyStructure) {
|
func LoadMail(mID string) (reader *bytes.Reader, structure *pkgMsg.BodyStructure) {
|
||||||
reader = &bytes.Reader{}
|
reader = &bytes.Reader{}
|
||||||
cacheMutex.Lock()
|
cacheMutex.Lock()
|
||||||
defer cacheMutex.Unlock()
|
defer cacheMutex.Unlock()
|
||||||
@ -115,7 +115,7 @@ func LoadMail(mID string) (reader *bytes.Reader, structure *backendMessage.BodyS
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func SaveMail(mID string, msg []byte, structure *backendMessage.BodyStructure) {
|
func SaveMail(mID string, msg []byte, structure *pkgMsg.BodyStructure) {
|
||||||
cacheMutex.Lock()
|
cacheMutex.Lock()
|
||||||
defer cacheMutex.Unlock()
|
defer cacheMutex.Unlock()
|
||||||
|
|
||||||
|
|||||||
4
internal/imap/cache/cache_test.go
vendored
4
internal/imap/cache/cache_test.go
vendored
@ -22,11 +22,11 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
bckMsg "github.com/ProtonMail/proton-bridge/pkg/message"
|
pkgMsg "github.com/ProtonMail/proton-bridge/pkg/message"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
var bs = &bckMsg.BodyStructure{} //nolint[gochecknoglobals]
|
var bs = &pkgMsg.BodyStructure{} //nolint[gochecknoglobals]
|
||||||
const testUID = "testmsg"
|
const testUID = "testmsg"
|
||||||
|
|
||||||
func TestSaveAndLoad(t *testing.T) {
|
func TestSaveAndLoad(t *testing.T) {
|
||||||
|
|||||||
@ -188,10 +188,23 @@ func (im *imapMailbox) Expunge() error {
|
|||||||
// the desired mailbox.
|
// the desired mailbox.
|
||||||
im.user.waitForAppend()
|
im.user.waitForAppend()
|
||||||
|
|
||||||
im.user.backend.setUpdatesBeBlocking(im.user.currentAddressLowercase, im.name, operationDeleteMessage)
|
im.user.backend.updates.block(im.user.currentAddressLowercase, im.name, operationDeleteMessage)
|
||||||
defer im.user.backend.unsetUpdatesBeBlocking(im.user.currentAddressLowercase, im.name, operationDeleteMessage)
|
defer im.user.backend.updates.unblock(im.user.currentAddressLowercase, im.name, operationDeleteMessage)
|
||||||
|
|
||||||
return im.storeMailbox.RemoveDeleted()
|
return im.storeMailbox.RemoveDeleted(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UIDExpunge permanently removes messages that have the \Deleted flag set
|
||||||
|
// and UID passed from SeqSet from the currently selected mailbox.
|
||||||
|
func (im *imapMailbox) UIDExpunge(seqSet *imap.SeqSet) error {
|
||||||
|
im.user.backend.updates.block(im.user.currentAddressLowercase, im.name, operationDeleteMessage)
|
||||||
|
defer im.user.backend.updates.unblock(im.user.currentAddressLowercase, im.name, operationDeleteMessage)
|
||||||
|
|
||||||
|
messageIDs, err := im.apiIDsFromSeqSet(true, seqSet)
|
||||||
|
if err != nil || len(messageIDs) == 0 {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return im.storeMailbox.RemoveDeleted(messageIDs)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (im *imapMailbox) ListQuotas() ([]string, error) {
|
func (im *imapMailbox) ListQuotas() ([]string, error) {
|
||||||
|
|||||||
@ -133,9 +133,6 @@ func (im *imapMailbox) CreateMessage(flags []string, date time.Time, body imap.L
|
|||||||
// We didn't find the message in the store, so we are currently sending it.
|
// We didn't find the message in the store, so we are currently sending it.
|
||||||
logEntry.WithField("time", date).Info("No matching UID, continuing APPEND to Sent")
|
logEntry.WithField("time", date).Info("No matching UID, continuing APPEND to Sent")
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is an APPEND to the Sent folder, so we will set the sent flag
|
|
||||||
m.Flags |= pmapi.FlagSent
|
|
||||||
}
|
}
|
||||||
|
|
||||||
message.ParseFlags(m, flags)
|
message.ParseFlags(m, flags)
|
||||||
@ -166,6 +163,13 @@ func (im *imapMailbox) CreateMessage(flags []string, date time.Time, body imap.L
|
|||||||
if err == nil && (im.user.user.IsCombinedAddressMode() || (im.storeAddress.AddressID() == msg.Message().AddressID)) {
|
if err == nil && (im.user.user.IsCombinedAddressMode() || (im.storeAddress.AddressID() == msg.Message().AddressID)) {
|
||||||
IDs := []string{internalID}
|
IDs := []string{internalID}
|
||||||
|
|
||||||
|
// See the comment bellow.
|
||||||
|
if msg.IsMarkedDeleted() {
|
||||||
|
if err := im.storeMailbox.MarkMessagesUndeleted(IDs); err != nil {
|
||||||
|
log.WithError(err).Error("Failed to undelete re-imported internal message")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
err = im.storeMailbox.LabelMessages(IDs)
|
err = im.storeMailbox.LabelMessages(IDs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -182,6 +186,20 @@ func (im *imapMailbox) CreateMessage(flags []string, date time.Time, body imap.L
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IMAP clients can move message to local folder (setting \Deleted flag)
|
||||||
|
// and then move it back (IMAP client does not remember the message,
|
||||||
|
// so instead removing the flag it imports duplicate message).
|
||||||
|
// Regular IMAP server would keep the message twice and later EXPUNGE would
|
||||||
|
// not delete the message (EXPUNGE would delete the original message and
|
||||||
|
// the new duplicate one would stay). API detects duplicates; therefore
|
||||||
|
// we need to remove \Deleted flag if IMAP client re-imports.
|
||||||
|
msg, err := im.storeMailbox.GetMessage(m.ID)
|
||||||
|
if err == nil && msg.IsMarkedDeleted() {
|
||||||
|
if err := im.storeMailbox.MarkMessagesUndeleted([]string{m.ID}); err != nil {
|
||||||
|
log.WithError(err).Error("Failed to undelete re-imported message")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
targetSeq := im.storeMailbox.GetUIDList([]string{m.ID})
|
targetSeq := im.storeMailbox.GetUIDList([]string{m.ID})
|
||||||
return uidplus.AppendResponse(im.storeMailbox.UIDValidity(), targetSeq)
|
return uidplus.AppendResponse(im.storeMailbox.UIDValidity(), targetSeq)
|
||||||
}
|
}
|
||||||
@ -219,7 +237,8 @@ func (im *imapMailbox) getMessage(storeMessage storeMessageProvider, items []ima
|
|||||||
msg.Envelope = message.GetEnvelope(m)
|
msg.Envelope = message.GetEnvelope(m)
|
||||||
case imap.FetchBody, imap.FetchBodyStructure:
|
case imap.FetchBody, imap.FetchBodyStructure:
|
||||||
var structure *message.BodyStructure
|
var structure *message.BodyStructure
|
||||||
if structure, _, err = im.getBodyStructure(storeMessage); err != nil {
|
structure, err = im.getBodyStructure(storeMessage)
|
||||||
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if msg.BodyStructure, err = structure.IMAPBodyStructure([]int{}); err != nil {
|
if msg.BodyStructure, err = structure.IMAPBodyStructure([]int{}); err != nil {
|
||||||
@ -242,7 +261,8 @@ func (im *imapMailbox) getMessage(storeMessage storeMessageProvider, items []ima
|
|||||||
// Size attribute on the server counts encrypted data. The value is cleared
|
// Size attribute on the server counts encrypted data. The value is cleared
|
||||||
// on our part and we need to compute "real" size of decrypted data.
|
// on our part and we need to compute "real" size of decrypted data.
|
||||||
if m.Size <= 0 {
|
if m.Size <= 0 {
|
||||||
if _, _, err = im.getBodyStructure(storeMessage); err != nil {
|
im.log.WithField("msgID", storeMessage.ID()).Trace("Size unknown - downloading body")
|
||||||
|
if _, _, err = im.getBodyAndStructure(storeMessage); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -277,7 +297,24 @@ func (im *imapMailbox) getLiteralForSection(itemSection imap.FetchItem, msg *ima
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (im *imapMailbox) getBodyStructure(storeMessage storeMessageProvider) (
|
func (im *imapMailbox) getBodyStructure(storeMessage storeMessageProvider) (bs *message.BodyStructure, err error) {
|
||||||
|
// Apple Mail requests body structure for all
|
||||||
|
// messages irregularly. We cache bodystructure in
|
||||||
|
// local database in order to not re-download all
|
||||||
|
// messages from server.
|
||||||
|
bs, err = storeMessage.GetBodyStructure()
|
||||||
|
if err != nil {
|
||||||
|
im.log.WithError(err).Debug("Fail to retrieve bodystructure from database")
|
||||||
|
}
|
||||||
|
if bs == nil {
|
||||||
|
if bs, _, err = im.getBodyAndStructure(storeMessage); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (im *imapMailbox) getBodyAndStructure(storeMessage storeMessageProvider) (
|
||||||
structure *message.BodyStructure,
|
structure *message.BodyStructure,
|
||||||
bodyReader *bytes.Reader, err error,
|
bodyReader *bytes.Reader, err error,
|
||||||
) {
|
) {
|
||||||
@ -287,14 +324,23 @@ func (im *imapMailbox) getBodyStructure(storeMessage storeMessageProvider) (
|
|||||||
if bodyReader, structure = cache.LoadMail(id); bodyReader.Len() == 0 || structure == nil {
|
if bodyReader, structure = cache.LoadMail(id); bodyReader.Len() == 0 || structure == nil {
|
||||||
var body []byte
|
var body []byte
|
||||||
structure, body, err = im.buildMessage(m)
|
structure, body, err = im.buildMessage(m)
|
||||||
if err == nil && structure != nil && len(body) > 0 {
|
m.Size = int64(len(body))
|
||||||
m.Size = int64(len(body))
|
// Save size and body structure even for messages unable to decrypt
|
||||||
if err := storeMessage.SetSize(m.Size); err != nil {
|
// so the size or body structure doesn't have to be computed every time.
|
||||||
|
if err := storeMessage.SetSize(m.Size); err != nil {
|
||||||
|
im.log.WithError(err).
|
||||||
|
WithField("newSize", m.Size).
|
||||||
|
WithField("msgID", m.ID).
|
||||||
|
Warn("Cannot update size while building")
|
||||||
|
}
|
||||||
|
if structure != nil && !isMessageInDraftFolder(m) {
|
||||||
|
if err := storeMessage.SetBodyStructure(structure); err != nil {
|
||||||
im.log.WithError(err).
|
im.log.WithError(err).
|
||||||
WithField("newSize", m.Size).
|
|
||||||
WithField("msgID", m.ID).
|
WithField("msgID", m.ID).
|
||||||
Warn("Cannot update size while building")
|
Warn("Cannot update bodystructure while building")
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if err == nil && structure != nil && len(body) > 0 {
|
||||||
if err := storeMessage.SetContentTypeAndHeader(m.MIMEType, m.Header); err != nil {
|
if err := storeMessage.SetContentTypeAndHeader(m.MIMEType, m.Header); err != nil {
|
||||||
im.log.WithError(err).
|
im.log.WithError(err).
|
||||||
WithField("msgID", m.ID).
|
WithField("msgID", m.ID).
|
||||||
@ -358,7 +404,7 @@ func (im *imapMailbox) getMessageBodySection(storeMessage storeMessageProvider,
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// The rest of cases need download and decrypt.
|
// The rest of cases need download and decrypt.
|
||||||
structure, bodyReader, err = im.getBodyStructure(storeMessage)
|
structure, bodyReader, err = im.getBodyAndStructure(storeMessage)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@ -46,8 +46,8 @@ func (im *imapMailbox) UpdateMessagesFlags(uid bool, seqSet *imap.SeqSet, operat
|
|||||||
// Called from go-imap in goroutines - we need to handle panics for each function.
|
// Called from go-imap in goroutines - we need to handle panics for each function.
|
||||||
defer im.panicHandler.HandlePanic()
|
defer im.panicHandler.HandlePanic()
|
||||||
|
|
||||||
im.user.backend.setUpdatesBeBlocking(im.user.currentAddressLowercase, im.name, operationUpdateMessage)
|
im.user.backend.updates.block(im.user.currentAddressLowercase, im.name, operationUpdateMessage)
|
||||||
defer im.user.backend.unsetUpdatesBeBlocking(im.user.currentAddressLowercase, im.name, operationUpdateMessage)
|
defer im.user.backend.updates.unblock(im.user.currentAddressLowercase, im.name, operationUpdateMessage)
|
||||||
|
|
||||||
messageIDs, err := im.apiIDsFromSeqSet(uid, seqSet)
|
messageIDs, err := im.apiIDsFromSeqSet(uid, seqSet)
|
||||||
if err != nil || len(messageIDs) == 0 {
|
if err != nil || len(messageIDs) == 0 {
|
||||||
@ -215,7 +215,7 @@ func (im *imapMailbox) MoveMessages(uid bool, seqSet *imap.SeqSet, targetLabel s
|
|||||||
return im.labelMessages(uid, seqSet, targetLabel, true)
|
return im.labelMessages(uid, seqSet, targetLabel, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (im *imapMailbox) labelMessages(uid bool, seqSet *imap.SeqSet, targetLabel string, move bool) error {
|
func (im *imapMailbox) labelMessages(uid bool, seqSet *imap.SeqSet, targetLabel string, move bool) error { //nolint[funlen]
|
||||||
messageIDs, err := im.apiIDsFromSeqSet(uid, seqSet)
|
messageIDs, err := im.apiIDsFromSeqSet(uid, seqSet)
|
||||||
if err != nil || len(messageIDs) == 0 {
|
if err != nil || len(messageIDs) == 0 {
|
||||||
return err
|
return err
|
||||||
@ -230,6 +230,25 @@ func (im *imapMailbox) labelMessages(uid bool, seqSet *imap.SeqSet, targetLabel
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Moving or copying from Inbox to Sent or from Sent to Inbox is no-op.
|
||||||
|
// Inbox and Sent is the same mailbox and message is showen in one or
|
||||||
|
// the other based on message flags.
|
||||||
|
// COPY operation has to be forbidden otherwise move by COPY+EXPUNGE
|
||||||
|
// would lead to message found only in All Mail, because COPY is no-op
|
||||||
|
// and EXPUNGE is translated as unlabel from the source.
|
||||||
|
// MOVE operation could be allowed, just it will do no change. It's better
|
||||||
|
// to refuse it as well so client is kept in proper state and no sync
|
||||||
|
// is needed.
|
||||||
|
isInboxOrSent := func(labelID string) bool {
|
||||||
|
return labelID == pmapi.InboxLabel || labelID == pmapi.SentLabel
|
||||||
|
}
|
||||||
|
if isInboxOrSent(im.storeMailbox.LabelID()) && isInboxOrSent(targetStoreMailbox.LabelID()) {
|
||||||
|
if im.storeMailbox.LabelID() == pmapi.InboxLabel {
|
||||||
|
return errors.New("move from Inbox to Sent is not allowed")
|
||||||
|
}
|
||||||
|
return errors.New("move from Sent to Inbox is not allowed")
|
||||||
|
}
|
||||||
|
|
||||||
deletedIDs := []string{}
|
deletedIDs := []string{}
|
||||||
allDeletedIDs, err := im.storeMailbox.GetDeletedAPIIDs()
|
allDeletedIDs, err := im.storeMailbox.GetDeletedAPIIDs()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -249,7 +268,10 @@ func (im *imapMailbox) labelMessages(uid bool, seqSet *imap.SeqSet, targetLabel
|
|||||||
if err := targetStoreMailbox.LabelMessages(messageIDs); err != nil {
|
if err := targetStoreMailbox.LabelMessages(messageIDs); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if move {
|
// Folder cannot be unlabeled. Every message has to belong to exactly one folder.
|
||||||
|
// In case of labeling message to folder, the original one is implicitly unlabeled.
|
||||||
|
// Therefore, we have to unlabel explicitly only if the source mailbox is label.
|
||||||
|
if im.storeMailbox.IsLabel() && move {
|
||||||
if err := im.storeMailbox.UnlabelMessages(messageIDs); err != nil {
|
if err := im.storeMailbox.UnlabelMessages(messageIDs); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -277,7 +299,7 @@ func (im *imapMailbox) SearchMessages(isUID bool, criteria *imap.SearchCriteria)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if criteria.Body != nil || criteria.Text != nil {
|
if criteria.Body != nil || criteria.Text != nil {
|
||||||
log.Warn("Body and Text criteria not applied.")
|
log.Warn("Body and Text criteria not applied")
|
||||||
}
|
}
|
||||||
|
|
||||||
var apiIDs []string
|
var apiIDs []string
|
||||||
@ -451,11 +473,13 @@ func (im *imapMailbox) ListMessages(isUID bool, seqSet *imap.SeqSet, items []ima
|
|||||||
im.panicHandler.HandlePanic()
|
im.panicHandler.HandlePanic()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// EXPUNGE cannot be sent during listing and can come only from
|
if !isUID {
|
||||||
// the event loop, so we prevent any server side update to avoid
|
// EXPUNGE cannot be sent during listing and can come only from
|
||||||
// the problem.
|
// the event loop, so we prevent any server side update to avoid
|
||||||
im.storeUser.PauseEventLoop(true)
|
// the problem.
|
||||||
defer im.storeUser.PauseEventLoop(false)
|
im.user.backend.updates.forbidExpunge(im.storeMailbox.LabelID())
|
||||||
|
defer im.user.backend.updates.allowExpunge(im.storeMailbox.LabelID())
|
||||||
|
}
|
||||||
|
|
||||||
var markAsReadIDs []string
|
var markAsReadIDs []string
|
||||||
markAsReadMutex := &sync.Mutex{}
|
markAsReadMutex := &sync.Mutex{}
|
||||||
|
|||||||
@ -23,6 +23,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
imapid "github.com/ProtonMail/go-imap-id"
|
imapid "github.com/ProtonMail/go-imap-id"
|
||||||
@ -31,6 +32,7 @@ import (
|
|||||||
"github.com/ProtonMail/proton-bridge/internal/imap/id"
|
"github.com/ProtonMail/proton-bridge/internal/imap/id"
|
||||||
"github.com/ProtonMail/proton-bridge/internal/imap/uidplus"
|
"github.com/ProtonMail/proton-bridge/internal/imap/uidplus"
|
||||||
"github.com/ProtonMail/proton-bridge/pkg/listener"
|
"github.com/ProtonMail/proton-bridge/pkg/listener"
|
||||||
|
"github.com/ProtonMail/proton-bridge/pkg/ports"
|
||||||
"github.com/emersion/go-imap"
|
"github.com/emersion/go-imap"
|
||||||
imapappendlimit "github.com/emersion/go-imap-appendlimit"
|
imapappendlimit "github.com/emersion/go-imap-appendlimit"
|
||||||
imapidle "github.com/emersion/go-imap-idle"
|
imapidle "github.com/emersion/go-imap-idle"
|
||||||
@ -43,14 +45,17 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type imapServer struct {
|
type imapServer struct {
|
||||||
|
panicHandler panicHandler
|
||||||
server *imapserver.Server
|
server *imapserver.Server
|
||||||
eventListener listener.Listener
|
eventListener listener.Listener
|
||||||
debugClient bool
|
debugClient bool
|
||||||
debugServer bool
|
debugServer bool
|
||||||
|
port int
|
||||||
|
isRunning atomic.Value
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewIMAPServer constructs a new IMAP server configured with the given options.
|
// NewIMAPServer constructs a new IMAP server configured with the given options.
|
||||||
func NewIMAPServer(debugClient, debugServer bool, port int, tls *tls.Config, imapBackend *imapBackend, eventListener listener.Listener) *imapServer { //nolint[golint]
|
func NewIMAPServer(panicHandler panicHandler, debugClient, debugServer bool, port int, tls *tls.Config, imapBackend *imapBackend, eventListener listener.Listener) *imapServer { //nolint[golint]
|
||||||
s := imapserver.New(imapBackend)
|
s := imapserver.New(imapBackend)
|
||||||
s.Addr = fmt.Sprintf("%v:%v", bridge.Host, port)
|
s.Addr = fmt.Sprintf("%v:%v", bridge.Host, port)
|
||||||
s.TLSConfig = tls
|
s.TLSConfig = tls
|
||||||
@ -58,6 +63,13 @@ func NewIMAPServer(debugClient, debugServer bool, port int, tls *tls.Config, ima
|
|||||||
s.ErrorLog = newServerErrorLogger("server-imap")
|
s.ErrorLog = newServerErrorLogger("server-imap")
|
||||||
s.AutoLogout = 30 * time.Minute
|
s.AutoLogout = 30 * time.Minute
|
||||||
|
|
||||||
|
if debugServer {
|
||||||
|
fmt.Println("THE LOG WILL CONTAIN **DECRYPTED** MESSAGE DATA")
|
||||||
|
log.Warning("================================================")
|
||||||
|
log.Warning("THIS LOG WILL CONTAIN **DECRYPTED** MESSAGE DATA")
|
||||||
|
log.Warning("================================================")
|
||||||
|
}
|
||||||
|
|
||||||
serverID := imapid.ID{
|
serverID := imapid.ID{
|
||||||
imapid.FieldName: "ProtonMail Bridge",
|
imapid.FieldName: "ProtonMail Bridge",
|
||||||
imapid.FieldVendor: "Proton Technologies AG",
|
imapid.FieldVendor: "Proton Technologies AG",
|
||||||
@ -88,23 +100,47 @@ func NewIMAPServer(debugClient, debugServer bool, port int, tls *tls.Config, ima
|
|||||||
uidplus.NewExtension(),
|
uidplus.NewExtension(),
|
||||||
)
|
)
|
||||||
|
|
||||||
return &imapServer{
|
server := &imapServer{
|
||||||
|
panicHandler: panicHandler,
|
||||||
server: s,
|
server: s,
|
||||||
eventListener: eventListener,
|
eventListener: eventListener,
|
||||||
debugClient: debugClient,
|
debugClient: debugClient,
|
||||||
debugServer: debugServer,
|
debugServer: debugServer,
|
||||||
|
port: port,
|
||||||
}
|
}
|
||||||
|
server.isRunning.Store(false)
|
||||||
|
return server
|
||||||
}
|
}
|
||||||
|
|
||||||
// Starts the server.
|
// Starts the server.
|
||||||
func (s *imapServer) ListenAndServe() {
|
func (s *imapServer) ListenAndServe() {
|
||||||
go s.monitorDisconnectedUsers()
|
go s.monitorDisconnectedUsers()
|
||||||
|
go s.monitorInternetConnection()
|
||||||
|
|
||||||
|
// When starting the Bridge, we don't want to retry to notify user
|
||||||
|
// quickly about the issue. Very probably retry will not help anyway.
|
||||||
|
s.listenAndServe(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *imapServer) listenAndServe(retries int) {
|
||||||
|
if s.isRunning.Load().(bool) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.isRunning.Store(true)
|
||||||
|
|
||||||
log.Info("IMAP server listening at ", s.server.Addr)
|
log.Info("IMAP server listening at ", s.server.Addr)
|
||||||
l, err := net.Listen("tcp", s.server.Addr)
|
l, err := net.Listen("tcp", s.server.Addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
s.isRunning.Store(false)
|
||||||
|
if retries > 0 {
|
||||||
|
log.WithError(err).WithField("retries", retries).Warn("IMAP listener failed")
|
||||||
|
time.Sleep(15 * time.Second)
|
||||||
|
s.listenAndServe(retries - 1)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.WithError(err).Error("IMAP listener failed")
|
||||||
s.eventListener.Emit(events.ErrorEvent, "IMAP failed: "+err.Error())
|
s.eventListener.Emit(events.ErrorEvent, "IMAP failed: "+err.Error())
|
||||||
log.Error("IMAP failed: ", err)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -112,9 +148,13 @@ func (s *imapServer) ListenAndServe() {
|
|||||||
Listener: l,
|
Listener: l,
|
||||||
server: s,
|
server: s,
|
||||||
})
|
})
|
||||||
if err != nil {
|
// Serve returns error every time, even after closing the server.
|
||||||
|
// User shouldn't be notified about error if server shouldn't be running,
|
||||||
|
// but it should in case it was not closed by `s.Close()`.
|
||||||
|
if err != nil && s.isRunning.Load().(bool) {
|
||||||
|
s.isRunning.Store(false)
|
||||||
|
log.WithError(err).Error("IMAP server failed")
|
||||||
s.eventListener.Emit(events.ErrorEvent, "IMAP failed: "+err.Error())
|
s.eventListener.Emit(events.ErrorEvent, "IMAP failed: "+err.Error())
|
||||||
log.Error("IMAP failed: ", err)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer s.server.Close() //nolint[errcheck]
|
defer s.server.Close() //nolint[errcheck]
|
||||||
@ -124,11 +164,56 @@ func (s *imapServer) ListenAndServe() {
|
|||||||
|
|
||||||
// Stops the server.
|
// Stops the server.
|
||||||
func (s *imapServer) Close() {
|
func (s *imapServer) Close() {
|
||||||
|
if !s.isRunning.Load().(bool) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.isRunning.Store(false)
|
||||||
|
|
||||||
|
log.Info("Closing IMAP server")
|
||||||
if err := s.server.Close(); err != nil {
|
if err := s.server.Close(); err != nil {
|
||||||
log.WithError(err).Error("Failed to close the connection")
|
log.WithError(err).Error("Failed to close the connection")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *imapServer) monitorInternetConnection() {
|
||||||
|
on := make(chan string)
|
||||||
|
s.eventListener.Add(events.InternetOnEvent, on)
|
||||||
|
off := make(chan string)
|
||||||
|
s.eventListener.Add(events.InternetOffEvent, off)
|
||||||
|
|
||||||
|
for {
|
||||||
|
var expectedIsPortFree bool
|
||||||
|
select {
|
||||||
|
case <-on:
|
||||||
|
go func() {
|
||||||
|
defer s.panicHandler.HandlePanic()
|
||||||
|
// We had issues on Mac that from time to time something
|
||||||
|
// blocked our port for a bit after we closed IMAP server
|
||||||
|
// due to connection issues.
|
||||||
|
// Restart always helped, so we do retry to not bother user.
|
||||||
|
s.listenAndServe(10)
|
||||||
|
}()
|
||||||
|
expectedIsPortFree = false
|
||||||
|
case <-off:
|
||||||
|
s.Close()
|
||||||
|
expectedIsPortFree = true
|
||||||
|
}
|
||||||
|
|
||||||
|
start := time.Now()
|
||||||
|
for {
|
||||||
|
if ports.IsPortFree(s.port) == expectedIsPortFree {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
// Safety stop if something went wrong.
|
||||||
|
if time.Since(start) > 15*time.Second {
|
||||||
|
log.WithField("expectedIsPortFree", expectedIsPortFree).Warn("Server start/stop check timeouted")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (s *imapServer) monitorDisconnectedUsers() {
|
func (s *imapServer) monitorDisconnectedUsers() {
|
||||||
ch := make(chan string)
|
ch := make(chan string)
|
||||||
s.eventListener.Add(events.CloseConnectionEvent, ch)
|
s.eventListener.Add(events.CloseConnectionEvent, ch)
|
||||||
|
|||||||
65
internal/imap/server_test.go
Normal file
65
internal/imap/server_test.go
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
// 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 imap
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ProtonMail/proton-bridge/internal/bridge"
|
||||||
|
"github.com/ProtonMail/proton-bridge/internal/events"
|
||||||
|
"github.com/ProtonMail/proton-bridge/pkg/listener"
|
||||||
|
"github.com/ProtonMail/proton-bridge/pkg/ports"
|
||||||
|
imapserver "github.com/emersion/go-imap/server"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
type testPanicHandler struct{}
|
||||||
|
|
||||||
|
func (ph *testPanicHandler) HandlePanic() {}
|
||||||
|
|
||||||
|
func TestIMAPServerTurnOffAndOnAgain(t *testing.T) {
|
||||||
|
panicHandler := &testPanicHandler{}
|
||||||
|
|
||||||
|
eventListener := listener.New()
|
||||||
|
|
||||||
|
port := ports.FindFreePortFrom(12345)
|
||||||
|
server := imapserver.New(nil)
|
||||||
|
server.Addr = fmt.Sprintf("%v:%v", bridge.Host, port)
|
||||||
|
|
||||||
|
s := &imapServer{
|
||||||
|
panicHandler: panicHandler,
|
||||||
|
server: server,
|
||||||
|
eventListener: eventListener,
|
||||||
|
}
|
||||||
|
s.isRunning.Store(false)
|
||||||
|
|
||||||
|
go s.ListenAndServe()
|
||||||
|
time.Sleep(5 * time.Second)
|
||||||
|
require.False(t, ports.IsPortFree(port))
|
||||||
|
|
||||||
|
eventListener.Emit(events.InternetOffEvent, "")
|
||||||
|
time.Sleep(10 * time.Second)
|
||||||
|
require.True(t, ports.IsPortFree(port))
|
||||||
|
|
||||||
|
eventListener.Emit(events.InternetOnEvent, "")
|
||||||
|
time.Sleep(10 * time.Second)
|
||||||
|
require.False(t, ports.IsPortFree(port))
|
||||||
|
}
|
||||||
@ -24,13 +24,14 @@ import (
|
|||||||
"github.com/ProtonMail/gopenpgp/v2/crypto"
|
"github.com/ProtonMail/gopenpgp/v2/crypto"
|
||||||
"github.com/ProtonMail/proton-bridge/internal/imap/uidplus"
|
"github.com/ProtonMail/proton-bridge/internal/imap/uidplus"
|
||||||
"github.com/ProtonMail/proton-bridge/internal/store"
|
"github.com/ProtonMail/proton-bridge/internal/store"
|
||||||
|
pkgMsg "github.com/ProtonMail/proton-bridge/pkg/message"
|
||||||
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
|
||||||
)
|
)
|
||||||
|
|
||||||
type storeUserProvider interface {
|
type storeUserProvider interface {
|
||||||
UserID() string
|
UserID() string
|
||||||
GetSpace() (usedSpace, maxSpace uint, err error)
|
GetSpace() (usedSpace, maxSpace uint, err error)
|
||||||
GetMaxUpload() (uint, error)
|
GetMaxUpload() (int64, error)
|
||||||
|
|
||||||
GetAddress(addressID string) (storeAddressProvider, error)
|
GetAddress(addressID string) (storeAddressProvider, error)
|
||||||
|
|
||||||
@ -42,8 +43,6 @@ type storeUserProvider interface {
|
|||||||
attachedPublicKeyName string,
|
attachedPublicKeyName string,
|
||||||
parentID string) (*pmapi.Message, []*pmapi.Attachment, error)
|
parentID string) (*pmapi.Message, []*pmapi.Attachment, error)
|
||||||
|
|
||||||
PauseEventLoop(bool)
|
|
||||||
|
|
||||||
SetChangeNotifier(store.ChangeNotifier)
|
SetChangeNotifier(store.ChangeNotifier)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,6 +62,7 @@ type storeMailboxProvider interface {
|
|||||||
Color() string
|
Color() string
|
||||||
IsSystem() bool
|
IsSystem() bool
|
||||||
IsFolder() bool
|
IsFolder() bool
|
||||||
|
IsLabel() bool
|
||||||
UIDValidity() uint32
|
UIDValidity() uint32
|
||||||
|
|
||||||
Rename(newName string) error
|
Rename(newName string) error
|
||||||
@ -89,7 +89,7 @@ type storeMailboxProvider interface {
|
|||||||
MarkMessagesDeleted(apiID []string) error
|
MarkMessagesDeleted(apiID []string) error
|
||||||
MarkMessagesUndeleted(apiID []string) error
|
MarkMessagesUndeleted(apiID []string) error
|
||||||
ImportMessage(msg *pmapi.Message, body []byte, labelIDs []string) error
|
ImportMessage(msg *pmapi.Message, body []byte, labelIDs []string) error
|
||||||
RemoveDeleted() error
|
RemoveDeleted(apiIDs []string) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type storeMessageProvider interface {
|
type storeMessageProvider interface {
|
||||||
@ -101,6 +101,8 @@ type storeMessageProvider interface {
|
|||||||
|
|
||||||
SetSize(int64) error
|
SetSize(int64) error
|
||||||
SetContentTypeAndHeader(string, mail.Header) error
|
SetContentTypeAndHeader(string, mail.Header) error
|
||||||
|
SetBodyStructure(*pkgMsg.BodyStructure) error
|
||||||
|
GetBodyStructure() (*pkgMsg.BodyStructure, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type storeUserWrap struct {
|
type storeUserWrap struct {
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user