You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
460 lines
16 KiB
460 lines
16 KiB
// 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/>. |
|
|
|
// Export dialog |
|
import QtQuick 2.8 |
|
import QtQuick.Controls 2.2 |
|
import ProtonUI 1.0 |
|
import ImportExportUI 1.0 |
|
|
|
// TODO |
|
// - make ErrorDialog module |
|
// - map decision to error code : ask (default), skip () |
|
// - what happens when import fails ? heuristic to find mail where to start from |
|
|
|
Dialog { |
|
id: root |
|
|
|
enum Page { |
|
LoadingStructure = 0, Options, Progress |
|
} |
|
|
|
title : set_title() |
|
|
|
property string account |
|
property string address |
|
property alias finish: finish |
|
|
|
property string msgClearUnfished: qsTr ("Remove already exported files.") |
|
|
|
isDialogBusy : true // currentIndex == 0 || currentIndex == 3 |
|
|
|
signal cancel() |
|
signal okay() |
|
|
|
|
|
Rectangle { // 0 |
|
id: dialogLoading |
|
width: root.width |
|
height: root.height |
|
color: Style.transparent |
|
|
|
Text { |
|
anchors.centerIn : dialogLoading |
|
font.pointSize: Style.dialog.titleSize * Style.pt |
|
color: Style.dialog.text |
|
horizontalAlignment: Text.AlignHCenter |
|
text: qsTr("Loading folders and labels for", "todo") +"\n" + address |
|
} |
|
} |
|
|
|
Rectangle { // 1 |
|
id: dialogInput |
|
width: root.width |
|
height: root.height |
|
color: Style.transparent |
|
|
|
Row { |
|
id: inputRow |
|
anchors { |
|
topMargin : root.titleHeight |
|
top : parent.top |
|
horizontalCenter : parent.horizontalCenter |
|
} |
|
spacing: 3*Style.main.leftMargin |
|
property real columnWidth : (root.width - Style.main.leftMargin - inputRow.spacing - Style.main.rightMargin) / 2 |
|
property real columnHeight : root.height - root.titleHeight - Style.main.leftMargin |
|
|
|
|
|
ExportStructure { |
|
id: sourceFoldersInput |
|
width : inputRow.columnWidth |
|
height : inputRow.columnHeight |
|
title : qsTr("From: %1", "todo").arg(address) |
|
} |
|
|
|
Column { |
|
spacing: (inputRow.columnHeight - dateRangeInput.height - outputFormatInput.height - outputPathInput.height - buttonRow.height - infotipEncrypted.height) / 4 |
|
|
|
DateRange{ |
|
id: dateRangeInput |
|
} |
|
|
|
OutputFormat { |
|
id: outputFormatInput |
|
} |
|
|
|
Row { |
|
spacing: Style.dialog.spacing |
|
CheckBoxLabel { |
|
id: exportEncrypted |
|
text: qsTr("Export emails that cannot be decrypted as ciphertext") |
|
anchors { |
|
bottom: parent.bottom |
|
bottomMargin: Style.dialog.fontSize/1.8 |
|
} |
|
} |
|
|
|
InfoToolTip { |
|
id: infotipEncrypted |
|
anchors { |
|
verticalCenter: exportEncrypted.verticalCenter |
|
} |
|
info: qsTr("Checking this option will export all emails that cannot be decrypted in ciphertext. If this option is not checked, these emails will not be exported", "todo") |
|
} |
|
} |
|
|
|
FileAndFolderSelect { |
|
id: outputPathInput |
|
title: qsTr("Select location of export:", "todo") |
|
width : inputRow.columnWidth // stretch folder input |
|
} |
|
|
|
Row { |
|
id: buttonRow |
|
anchors.right : parent.right |
|
spacing : Style.dialog.rightMargin |
|
|
|
ButtonRounded { |
|
id:buttonCancel |
|
fa_icon: Style.fa.times |
|
text: qsTr("Cancel") |
|
color_main: Style.main.textBlue |
|
onClicked : root.cancel() |
|
} |
|
|
|
ButtonRounded { |
|
id: buttonNext |
|
fa_icon: Style.fa.check |
|
text: qsTr("Export","todo") |
|
enabled: transferRules != 0 |
|
color_main: Style.dialog.background |
|
color_minor: enabled ? Style.dialog.textBlue : Style.main.textDisabled |
|
isOpaque: true |
|
onClicked : root.okay() |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
Rectangle { // 2 |
|
id: progressStatus |
|
width: root.width |
|
height: root.height |
|
color: "transparent" |
|
|
|
Row { |
|
anchors { |
|
bottom: progressbarExport.top |
|
bottomMargin: Style.dialog.heightSeparator |
|
left: progressbarExport.left |
|
} |
|
spacing: Style.main.rightMargin |
|
AccessibleText { |
|
id: statusLabel |
|
text : qsTr("Status:") |
|
font.pointSize: Style.main.iconSize * Style.pt |
|
color : Style.main.text |
|
} |
|
AccessibleText { |
|
anchors.baseline: statusLabel.baseline |
|
text : { |
|
if (progressbarExport.isFinished) return qsTr("finished") |
|
if (go.progressDescription == "") return qsTr("exporting") |
|
return go.progressDescription |
|
} |
|
elide: Text.ElideMiddle |
|
width: progressbarExport.width - parent.spacing - statusLabel.width |
|
font.pointSize: Style.dialog.textSize * Style.pt |
|
color : Style.main.textDisabled |
|
} |
|
} |
|
|
|
ProgressBar { |
|
id: progressbarExport |
|
implicitWidth : 2*progressStatus.width/3 |
|
implicitHeight : Style.exporting.rowHeight |
|
value: go.progress |
|
property int current: go.total * go.progress |
|
property bool isFinished: finishedPartBar.width == progressbarExport.width |
|
anchors { |
|
centerIn: parent |
|
} |
|
background: Rectangle { |
|
radius : Style.exporting.boxRadius |
|
color : Style.exporting.progressBackground |
|
} |
|
contentItem: Item { |
|
Rectangle { |
|
id: finishedPartBar |
|
width : parent.width * progressbarExport.visualPosition |
|
height : parent.height |
|
radius : Style.exporting.boxRadius |
|
gradient : Gradient { |
|
GradientStop { position: 0.00; color: Qt.lighter(Style.exporting.progressStatus,1.1) } |
|
GradientStop { position: 0.66; color: Style.exporting.progressStatus } |
|
GradientStop { position: 1.00; color: Qt.darker(Style.exporting.progressStatus,1.1) } |
|
} |
|
|
|
Behavior on width { |
|
NumberAnimation { duration:800; easing.type: Easing.InOutQuad } |
|
} |
|
} |
|
Text { |
|
anchors.centerIn: parent |
|
text: { |
|
if (progressbarExport.isFinished) { |
|
if (go.progressDescription=="") return qsTr("Export finished","todo") |
|
else return qsTr("Export failed: %1").arg(go.progressDescription) |
|
} |
|
if ( |
|
go.progressDescription == gui.enums.progressInit || |
|
(go.progress==0 && go.description=="") |
|
) { |
|
if (go.total>1) return qsTr("Estimating the total number of messages (%1)","todo").arg(go.total) |
|
else return qsTr("Estimating the total number of messages","todo") |
|
} |
|
var msg = qsTr("Exporting message %1 of %2 (%3%)","todo") |
|
if (pauseButton.paused) msg = qsTr("Exporting paused at message %1 of %2 (%3%)","todo") |
|
return msg.arg(progressbarExport.current).arg(go.total).arg(Math.floor(go.progress*100)) |
|
} |
|
color: Style.main.background |
|
font { |
|
pointSize: Style.dialog.fontSize * Style.pt |
|
} |
|
} |
|
} |
|
} |
|
|
|
Row { |
|
anchors { |
|
top: progressbarExport.bottom |
|
topMargin : Style.dialog.heightSeparator |
|
horizontalCenter: parent.horizontalCenter |
|
} |
|
spacing: Style.dialog.rightMargin |
|
|
|
ButtonRounded { |
|
id: pauseButton |
|
property bool paused : false |
|
fa_icon : paused ? Style.fa.play : Style.fa.pause |
|
text : paused ? qsTr("Resume") : qsTr("Pause") |
|
color_main : Style.dialog.textBlue |
|
onClicked : { |
|
if (paused) { |
|
if (winMain.updateState == gui.enums.statusNoInternet) { |
|
go.notifyError(gui.enums.errNoInternet) |
|
return |
|
} |
|
go.resumeProcess() |
|
} else { |
|
go.pauseProcess() |
|
} |
|
paused = !paused |
|
pauseButton.focus=false |
|
} |
|
visible : !progressbarExport.isFinished |
|
} |
|
|
|
ButtonRounded { |
|
fa_icon : Style.fa.times |
|
text : qsTr("Cancel") |
|
color_main : Style.dialog.textBlue |
|
visible : !progressbarExport.isFinished |
|
onClicked : root.ask_cancel_progress() |
|
} |
|
|
|
ButtonRounded { |
|
id: finish |
|
fa_icon : Style.fa.check |
|
text : qsTr("Okay","todo") |
|
color_main : Style.dialog.background |
|
color_minor : Style.dialog.textBlue |
|
isOpaque : true |
|
visible : progressbarExport.isFinished |
|
onClicked : root.okay() |
|
} |
|
} |
|
|
|
ClickIconText { |
|
id: buttonHelp |
|
anchors { |
|
right : parent.right |
|
bottom : parent.bottom |
|
rightMargin : Style.main.rightMargin |
|
bottomMargin : Style.main.rightMargin |
|
} |
|
textColor : Style.main.textDisabled |
|
iconText : Style.fa.question_circle |
|
text : qsTr("Help", "directs the user to the online user guide") |
|
textBold : true |
|
onClicked : Qt.openUrlExternally("https://protonmail.com/support/categories/import-export/") |
|
} |
|
} |
|
|
|
PopupMessage { |
|
id: errorPopup |
|
width: root.width |
|
height: root.height |
|
} |
|
|
|
function check_inputs() { |
|
if (currentIndex == 1) { |
|
// at least one email to export |
|
if (transferRules.rowCount() == 0){ |
|
errorPopup.show(qsTr("No emails found to export. Please try another address.", "todo")) |
|
return false |
|
} |
|
// at least one source selected |
|
/* |
|
if (!transferRules.atLeastOneSelected) { |
|
errorPopup.show(qsTr("Please select at least one item to export.", "todo")) |
|
return false |
|
} |
|
*/ |
|
// check path |
|
var folderCheck = go.checkPathStatus(outputPathInput.path) |
|
switch (folderCheck) { |
|
case gui.enums.pathEmptyPath: |
|
errorPopup.show(qsTr("Missing export path. Please select an output folder.")) |
|
break; |
|
case gui.enums.pathWrongPath: |
|
errorPopup.show(qsTr("Folder '%1' not found. Please select an output folder.").arg(outputPathInput.path)) |
|
break; |
|
case gui.enums.pathOK | gui.enums.pathNotADir: |
|
errorPopup.show(qsTr("File '%1' is not a folder. Please select an output folder.").arg(outputPathInput.path)) |
|
break; |
|
case gui.enums.pathWrongPermissions: |
|
errorPopup.show(qsTr("Cannot access folder '%1'. Please check folder permissions.").arg(outputPathInput.path)) |
|
break; |
|
} |
|
if ( |
|
(folderCheck&gui.enums.pathOK)==0 || |
|
(folderCheck&gui.enums.pathNotADir)==gui.enums.pathNotADir |
|
) return false |
|
if (winMain.updateState == gui.enums.statusNoInternet) { |
|
errorPopup.show(qsTr("Please check your internet connection.")) |
|
return false |
|
} |
|
} |
|
return true |
|
} |
|
|
|
function set_title() { |
|
switch(root.currentIndex){ |
|
case 1 : return qsTr("Select what you'd like to export:") |
|
default: return "" |
|
} |
|
} |
|
|
|
function clear_status() { |
|
go.progress=0.0 |
|
go.total=0.0 |
|
go.progressDescription=gui.enums.progressInit |
|
} |
|
|
|
function ask_cancel_progress(){ |
|
errorPopup.buttonYes.visible = true |
|
errorPopup.buttonNo.visible = true |
|
errorPopup.buttonOkay.visible = false |
|
errorPopup.show ("Are you sure you want to cancel this export?") |
|
} |
|
|
|
|
|
onCancel : { |
|
switch (root.currentIndex) { |
|
case 0 : |
|
case 1 : root.hide(); break; |
|
case 2 : // progress bar |
|
go.cancelProcess(); |
|
// no break |
|
default: |
|
root.clear_status() |
|
root.currentIndex=1 |
|
} |
|
} |
|
|
|
onOkay : { |
|
var isOK = check_inputs() |
|
if (!isOK) return |
|
timer.interval= currentIndex==1 ? 1 : 300 |
|
switch (root.currentIndex) { |
|
case 2: // progress |
|
root.clear_status() |
|
root.hide() |
|
break |
|
case 0: // loading structure |
|
dateRangeInput.getRange() |
|
//no break |
|
default: |
|
incrementCurrentIndex() |
|
timer.start() |
|
} |
|
} |
|
|
|
onShow: { |
|
if (winMain.updateState==gui.enums.statusNoInternet) { |
|
go.checkInternet() |
|
if (winMain.updateState==gui.enums.statusNoInternet) { |
|
go.notifyError(gui.enums.errNoInternet) |
|
root.hide() |
|
return |
|
} |
|
} |
|
|
|
root.clear_status() |
|
root.currentIndex=0 |
|
timer.interval = 300 |
|
timer.start() |
|
dateRangeInput.allDates = true |
|
} |
|
|
|
Connections { |
|
target: timer |
|
onTriggered : { |
|
switch (currentIndex) { |
|
case 0: |
|
go.loadStructureForExport(root.account, root.address) |
|
sourceFoldersInput.hasItems = (transferRules.rowCount() > 0) |
|
break |
|
case 2: |
|
dateRangeInput.applyRange() |
|
go.startExport( |
|
outputPathInput.path, |
|
root.address, |
|
outputFormatInput.checkedText, |
|
exportEncrypted.checked |
|
) |
|
break |
|
} |
|
} |
|
} |
|
|
|
Connections { |
|
target: errorPopup |
|
|
|
onClickedOkay : errorPopup.hide() |
|
onClickedYes : { |
|
root.cancel() |
|
errorPopup.hide() |
|
} |
|
onClickedNo : { |
|
errorPopup.hide() |
|
} |
|
} |
|
}
|
|
|