mirror of
https://github.com/ProtonMail/proton-bridge.git
synced 2025-12-17 23:56:56 +00:00
Import/Export GUI
This commit is contained in:
committed by
Michal Horejsek
parent
1c10cc5065
commit
7e5e3d3dd4
@ -43,10 +43,10 @@ const (
|
||||
// Constants for data map
|
||||
const (
|
||||
// Account info
|
||||
Account = int(core.Qt__UserRole) + 1<<iota
|
||||
Status
|
||||
Password
|
||||
Aliases
|
||||
Account = int(core.Qt__UserRole) + 1 + iota // 256 + 1 = 257
|
||||
Status // 258
|
||||
Password // 259
|
||||
Aliases // ...
|
||||
IsExpanded
|
||||
// Folder info
|
||||
FolderId
|
||||
@ -65,4 +65,56 @@ const (
|
||||
MailFrom
|
||||
InputFolder
|
||||
ErrorMessage
|
||||
// Transfer rules mbox
|
||||
MboxSelectedIndex
|
||||
MboxIsActive
|
||||
MboxID
|
||||
MboxName
|
||||
MboxType
|
||||
MboxColor
|
||||
// Transfer Rules
|
||||
RuleTargetLabelColors
|
||||
RuleFromDate
|
||||
RuleToDate
|
||||
)
|
||||
|
||||
const (
|
||||
// This should match enums in GuiIE.qml
|
||||
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
|
||||
)
|
||||
|
||||
@ -21,14 +21,10 @@ package qtie
|
||||
|
||||
import (
|
||||
qtcommon "github.com/ProtonMail/proton-bridge/internal/frontend/qt-common"
|
||||
"github.com/ProtonMail/proton-bridge/internal/transfer"
|
||||
"github.com/therecipe/qt/core"
|
||||
)
|
||||
|
||||
// ErrorDetail stores information about email and error
|
||||
type ErrorDetail struct {
|
||||
MailSubject, MailDate, MailFrom, InputFolder, ErrorMessage string
|
||||
}
|
||||
|
||||
func init() {
|
||||
ErrorListModel_QRegisterMetaType()
|
||||
}
|
||||
@ -42,11 +38,12 @@ type ErrorListModel struct {
|
||||
_ map[int]*core.QByteArray `property:"roles"`
|
||||
_ int `property:"count"`
|
||||
|
||||
Details []*ErrorDetail
|
||||
Progress *transfer.Progress
|
||||
records []*transfer.MessageStatus
|
||||
}
|
||||
|
||||
func (s *ErrorListModel) init() {
|
||||
s.SetRoles(map[int]*core.QByteArray{
|
||||
func (e *ErrorListModel) init() {
|
||||
e.SetRoles(map[int]*core.QByteArray{
|
||||
MailSubject: qtcommon.NewQByteArrayFromString("mailSubject"),
|
||||
MailDate: qtcommon.NewQByteArrayFromString("mailDate"),
|
||||
MailFrom: qtcommon.NewQByteArrayFromString("mailFrom"),
|
||||
@ -54,76 +51,50 @@ func (s *ErrorListModel) init() {
|
||||
ErrorMessage: qtcommon.NewQByteArrayFromString("errorMessage"),
|
||||
})
|
||||
// basic QAbstractListModel mehods
|
||||
s.ConnectData(s.data)
|
||||
s.ConnectRowCount(s.rowCount)
|
||||
s.ConnectColumnCount(s.columnCount)
|
||||
s.ConnectRoleNames(s.roleNames)
|
||||
e.ConnectData(e.data)
|
||||
e.ConnectRowCount(e.rowCount)
|
||||
e.ConnectColumnCount(e.columnCount)
|
||||
e.ConnectRoleNames(e.roleNames)
|
||||
}
|
||||
|
||||
func (s *ErrorListModel) data(index *core.QModelIndex, role int) *core.QVariant {
|
||||
func (e *ErrorListModel) data(index *core.QModelIndex, role int) *core.QVariant {
|
||||
if !index.IsValid() {
|
||||
return core.NewQVariant()
|
||||
}
|
||||
|
||||
if index.Row() >= len(s.Details) {
|
||||
if index.Row() >= len(e.records) {
|
||||
return core.NewQVariant()
|
||||
}
|
||||
|
||||
var p = s.Details[index.Row()]
|
||||
var r = e.records[index.Row()]
|
||||
|
||||
switch role {
|
||||
case MailSubject:
|
||||
return qtcommon.NewQVariantString(p.MailSubject)
|
||||
return qtcommon.NewQVariantString(r.Subject)
|
||||
case MailDate:
|
||||
return qtcommon.NewQVariantString(p.MailDate)
|
||||
return qtcommon.NewQVariantString(r.Time.String())
|
||||
case MailFrom:
|
||||
return qtcommon.NewQVariantString(p.MailFrom)
|
||||
return qtcommon.NewQVariantString(r.From)
|
||||
case InputFolder:
|
||||
return qtcommon.NewQVariantString(p.InputFolder)
|
||||
return qtcommon.NewQVariantString(r.SourceID)
|
||||
case ErrorMessage:
|
||||
return qtcommon.NewQVariantString(p.ErrorMessage)
|
||||
return qtcommon.NewQVariantString(r.GetErrorMessage())
|
||||
default:
|
||||
return core.NewQVariant()
|
||||
}
|
||||
}
|
||||
|
||||
func (s *ErrorListModel) rowCount(parent *core.QModelIndex) int { return len(s.Details) }
|
||||
func (s *ErrorListModel) columnCount(parent *core.QModelIndex) int { return 1 }
|
||||
func (s *ErrorListModel) roleNames() map[int]*core.QByteArray { return s.Roles() }
|
||||
func (e *ErrorListModel) rowCount(parent *core.QModelIndex) int { return len(e.records) }
|
||||
func (e *ErrorListModel) columnCount(parent *core.QModelIndex) int { return 1 }
|
||||
func (e *ErrorListModel) roleNames() map[int]*core.QByteArray { return e.Roles() }
|
||||
|
||||
// Add more errors to list
|
||||
func (s *ErrorListModel) Add(more []*ErrorDetail) {
|
||||
s.BeginInsertRows(core.NewQModelIndex(), len(s.Details), len(s.Details))
|
||||
s.Details = append(s.Details, more...)
|
||||
s.SetCount(len(s.Details))
|
||||
s.EndInsertRows()
|
||||
}
|
||||
func (e *ErrorListModel) load() {
|
||||
if e.Progress == nil {
|
||||
log.Error("Progress not connected")
|
||||
return
|
||||
}
|
||||
|
||||
// Clear removes all items in model
|
||||
func (s *ErrorListModel) Clear() {
|
||||
s.BeginRemoveRows(core.NewQModelIndex(), 0, len(s.Details))
|
||||
s.Details = s.Details[0:0]
|
||||
s.SetCount(len(s.Details))
|
||||
s.EndRemoveRows()
|
||||
}
|
||||
|
||||
func (s *ErrorListModel) load(importLogFileName string) {
|
||||
/*
|
||||
err := backend.LoopDetailsInFile(importLogFileName, func(d *backend.MessageDetails) {
|
||||
if d.MessageID != "" { // imported ok
|
||||
return
|
||||
}
|
||||
ed := &ErrorDetail{
|
||||
MailSubject: d.Subject,
|
||||
MailDate: d.Time,
|
||||
MailFrom: d.From,
|
||||
InputFolder: d.Folder,
|
||||
ErrorMessage: d.Error,
|
||||
}
|
||||
s.Add([]*ErrorDetail{ed})
|
||||
})
|
||||
if err != nil {
|
||||
log.Errorf("load import report from %q: %v", importLogFileName, err)
|
||||
}
|
||||
*/
|
||||
e.BeginResetModel()
|
||||
e.records = e.Progress.GetFailedMessages()
|
||||
e.EndResetModel()
|
||||
}
|
||||
|
||||
@ -21,6 +21,7 @@ package qtie
|
||||
|
||||
import (
|
||||
"github.com/ProtonMail/proton-bridge/internal/transfer"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -29,10 +30,11 @@ const (
|
||||
)
|
||||
|
||||
func (f *FrontendQt) LoadStructureForExport(addressOrID string) {
|
||||
errCode := errUnknownError
|
||||
var err error
|
||||
defer func() {
|
||||
if err != nil {
|
||||
f.showError(err)
|
||||
f.showError(errCode, errors.Wrap(err, "failed to load structure for "+addressOrID))
|
||||
f.Qml.ExportStructureLoadFinished(false)
|
||||
} else {
|
||||
f.Qml.ExportStructureLoadFinished(true)
|
||||
@ -40,20 +42,12 @@ func (f *FrontendQt) LoadStructureForExport(addressOrID string) {
|
||||
}()
|
||||
|
||||
if f.transfer, err = f.ie.GetEMLExporter(addressOrID, ""); err != nil {
|
||||
// The only error can be problem to load PM user and address.
|
||||
errCode = errPMLoadFailed
|
||||
return
|
||||
}
|
||||
|
||||
f.PMStructure.Clear()
|
||||
sourceMailboxes, err := f.transfer.SourceMailboxes()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, mbox := range sourceMailboxes {
|
||||
rule := f.transfer.GetRule(mbox)
|
||||
f.PMStructure.addEntry(newFolderInfo(mbox, rule))
|
||||
}
|
||||
|
||||
f.PMStructure.transfer = f.transfer
|
||||
f.TransferRules.setTransfer(f.transfer)
|
||||
}
|
||||
|
||||
func (f *FrontendQt) StartExport(rootPath, login, fileType string, attachEncryptedBody bool) {
|
||||
|
||||
@ -65,11 +65,11 @@ type FrontendQt struct {
|
||||
programVersion string // Program version
|
||||
buildVersion string // Program build version
|
||||
|
||||
PMStructure *FolderStructure // Providing data for account labels and folders for ProtonMail account
|
||||
ExternalStructure *FolderStructure // Providing data for account labels and folders for MBOX, EML or external IMAP account
|
||||
ErrorList *ErrorListModel // Providing data for error reporting
|
||||
TransferRules *TransferRules
|
||||
ErrorList *ErrorListModel // Providing data for error reporting
|
||||
|
||||
transfer *transfer.Transfer
|
||||
progress *transfer.Progress
|
||||
|
||||
notifyHasNoKeychain bool
|
||||
}
|
||||
@ -103,102 +103,99 @@ func New(
|
||||
}
|
||||
|
||||
// IsAppRestarting for Import-Export is always false i.e never restarts
|
||||
func (s *FrontendQt) IsAppRestarting() bool {
|
||||
func (f *FrontendQt) IsAppRestarting() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// Loop function for Import-Export interface. It runs QtExecute in main thread
|
||||
// with no additional function.
|
||||
func (s *FrontendQt) Loop(setupError error) (err error) {
|
||||
func (f *FrontendQt) Loop(setupError error) (err error) {
|
||||
if setupError != nil {
|
||||
s.notifyHasNoKeychain = true
|
||||
f.notifyHasNoKeychain = true
|
||||
}
|
||||
go func() {
|
||||
defer s.panicHandler.HandlePanic()
|
||||
s.watchEvents()
|
||||
defer f.panicHandler.HandlePanic()
|
||||
f.watchEvents()
|
||||
}()
|
||||
err = s.QtExecute(func(s *FrontendQt) error { return nil })
|
||||
err = f.QtExecute(func(f *FrontendQt) error { return nil })
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *FrontendQt) watchEvents() {
|
||||
internetOffCh := qtcommon.MakeAndRegisterEvent(s.eventListener, events.InternetOffEvent)
|
||||
internetOnCh := qtcommon.MakeAndRegisterEvent(s.eventListener, events.InternetOnEvent)
|
||||
restartBridgeCh := qtcommon.MakeAndRegisterEvent(s.eventListener, events.RestartBridgeEvent)
|
||||
addressChangedCh := qtcommon.MakeAndRegisterEvent(s.eventListener, events.AddressChangedEvent)
|
||||
addressChangedLogoutCh := qtcommon.MakeAndRegisterEvent(s.eventListener, events.AddressChangedLogoutEvent)
|
||||
logoutCh := qtcommon.MakeAndRegisterEvent(s.eventListener, events.LogoutEvent)
|
||||
updateApplicationCh := qtcommon.MakeAndRegisterEvent(s.eventListener, events.UpgradeApplicationEvent)
|
||||
newUserCh := qtcommon.MakeAndRegisterEvent(s.eventListener, events.UserRefreshEvent)
|
||||
func (f *FrontendQt) watchEvents() {
|
||||
internetOffCh := qtcommon.MakeAndRegisterEvent(f.eventListener, events.InternetOffEvent)
|
||||
internetOnCh := qtcommon.MakeAndRegisterEvent(f.eventListener, events.InternetOnEvent)
|
||||
restartBridgeCh := qtcommon.MakeAndRegisterEvent(f.eventListener, events.RestartBridgeEvent)
|
||||
addressChangedCh := qtcommon.MakeAndRegisterEvent(f.eventListener, events.AddressChangedEvent)
|
||||
addressChangedLogoutCh := qtcommon.MakeAndRegisterEvent(f.eventListener, events.AddressChangedLogoutEvent)
|
||||
logoutCh := qtcommon.MakeAndRegisterEvent(f.eventListener, events.LogoutEvent)
|
||||
updateApplicationCh := qtcommon.MakeAndRegisterEvent(f.eventListener, events.UpgradeApplicationEvent)
|
||||
newUserCh := qtcommon.MakeAndRegisterEvent(f.eventListener, events.UserRefreshEvent)
|
||||
for {
|
||||
select {
|
||||
case <-internetOffCh:
|
||||
s.Qml.SetConnectionStatus(false)
|
||||
f.Qml.SetConnectionStatus(false)
|
||||
case <-internetOnCh:
|
||||
s.Qml.SetConnectionStatus(true)
|
||||
f.Qml.SetConnectionStatus(true)
|
||||
case <-restartBridgeCh:
|
||||
s.Qml.SetIsRestarting(true)
|
||||
s.App.Quit()
|
||||
f.Qml.SetIsRestarting(true)
|
||||
f.App.Quit()
|
||||
case address := <-addressChangedCh:
|
||||
s.Qml.NotifyAddressChanged(address)
|
||||
f.Qml.NotifyAddressChanged(address)
|
||||
case address := <-addressChangedLogoutCh:
|
||||
s.Qml.NotifyAddressChangedLogout(address)
|
||||
f.Qml.NotifyAddressChangedLogout(address)
|
||||
case userID := <-logoutCh:
|
||||
user, err := s.ie.GetUser(userID)
|
||||
user, err := f.ie.GetUser(userID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
s.Qml.NotifyLogout(user.Username())
|
||||
f.Qml.NotifyLogout(user.Username())
|
||||
case <-updateApplicationCh:
|
||||
s.Qml.ProcessFinished()
|
||||
s.Qml.NotifyUpdate()
|
||||
f.Qml.ProcessFinished()
|
||||
f.Qml.NotifyUpdate()
|
||||
case <-newUserCh:
|
||||
s.Qml.LoadAccounts()
|
||||
f.Qml.LoadAccounts()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *FrontendQt) qtSetupQmlAndStructures() {
|
||||
s.App = widgets.NewQApplication(len(os.Args), os.Args)
|
||||
func (f *FrontendQt) qtSetupQmlAndStructures() {
|
||||
f.App = widgets.NewQApplication(len(os.Args), os.Args)
|
||||
// view
|
||||
s.View = qml.NewQQmlApplicationEngine(s.App)
|
||||
f.View = qml.NewQQmlApplicationEngine(f.App)
|
||||
// Add Go-QML Import-Export
|
||||
s.Qml = NewGoQMLInterface(nil)
|
||||
s.Qml.SetFrontend(s) // provides access
|
||||
s.View.RootContext().SetContextProperty("go", s.Qml)
|
||||
f.Qml = NewGoQMLInterface(nil)
|
||||
f.Qml.SetFrontend(f) // provides access
|
||||
f.View.RootContext().SetContextProperty("go", f.Qml)
|
||||
|
||||
// Add AccountsModel
|
||||
s.Accounts.SetupAccounts(s.Qml, s.ie)
|
||||
s.View.RootContext().SetContextProperty("accountsModel", s.Accounts.Model)
|
||||
f.Accounts.SetupAccounts(f.Qml, f.ie)
|
||||
f.View.RootContext().SetContextProperty("accountsModel", f.Accounts.Model)
|
||||
|
||||
// Add ProtonMail FolderStructure
|
||||
s.PMStructure = NewFolderStructure(nil)
|
||||
s.View.RootContext().SetContextProperty("structurePM", s.PMStructure)
|
||||
|
||||
// Add external FolderStructure
|
||||
s.ExternalStructure = NewFolderStructure(nil)
|
||||
s.View.RootContext().SetContextProperty("structureExternal", s.ExternalStructure)
|
||||
// Add TransferRules structure
|
||||
f.TransferRules = NewTransferRules(nil)
|
||||
f.View.RootContext().SetContextProperty("transferRules", f.TransferRules)
|
||||
|
||||
// Add error list modal
|
||||
s.ErrorList = NewErrorListModel(nil)
|
||||
s.View.RootContext().SetContextProperty("errorList", s.ErrorList)
|
||||
s.Qml.ConnectLoadImportReports(s.ErrorList.load)
|
||||
f.ErrorList = NewErrorListModel(nil)
|
||||
f.View.RootContext().SetContextProperty("errorList", f.ErrorList)
|
||||
f.Qml.ConnectLoadImportReports(f.ErrorList.load)
|
||||
|
||||
// Import path and load QML files
|
||||
s.View.AddImportPath("qrc:///")
|
||||
s.View.Load(core.NewQUrl3("qrc:/uiie.qml", 0))
|
||||
f.View.AddImportPath("qrc:///")
|
||||
f.View.Load(core.NewQUrl3("qrc:/uiie.qml", 0))
|
||||
|
||||
// TODO set the first start flag
|
||||
log.Error("Get FirstStart: Not implemented")
|
||||
//if prefs.Get(prefs.FirstStart) == "true" {
|
||||
if false {
|
||||
s.Qml.SetIsFirstStart(true)
|
||||
f.Qml.SetIsFirstStart(true)
|
||||
} else {
|
||||
s.Qml.SetIsFirstStart(false)
|
||||
f.Qml.SetIsFirstStart(false)
|
||||
}
|
||||
|
||||
// Notify user about error during initialization.
|
||||
if s.notifyHasNoKeychain {
|
||||
s.Qml.NotifyHasNoKeychain()
|
||||
if f.notifyHasNoKeychain {
|
||||
f.Qml.NotifyHasNoKeychain()
|
||||
}
|
||||
}
|
||||
|
||||
@ -207,18 +204,18 @@ func (s *FrontendQt) qtSetupQmlAndStructures() {
|
||||
// It is needed to have just one Qt application per program (at least per same
|
||||
// thread). This functions reads the main user interface defined in QML files.
|
||||
// The files are appended to library by Qt-QRC.
|
||||
func (s *FrontendQt) QtExecute(Procedure func(*FrontendQt) error) error {
|
||||
qtcommon.QtSetupCoreAndControls(s.programName, s.programVersion)
|
||||
s.qtSetupQmlAndStructures()
|
||||
func (f *FrontendQt) QtExecute(Procedure func(*FrontendQt) error) error {
|
||||
qtcommon.QtSetupCoreAndControls(f.programName, f.programVersion)
|
||||
f.qtSetupQmlAndStructures()
|
||||
// Check QML is loaded properly
|
||||
if len(s.View.RootObjects()) == 0 {
|
||||
if len(f.View.RootObjects()) == 0 {
|
||||
//return errors.New(errors.ErrQApplication, "QML not loaded properly")
|
||||
return errors.New("QML not loaded properly")
|
||||
}
|
||||
// Obtain main window (need for invoke method)
|
||||
s.MainWin = s.View.RootObjects()[0]
|
||||
f.MainWin = f.View.RootObjects()[0]
|
||||
// Injected procedure for out-of-main-thread applications
|
||||
if err := Procedure(s); err != nil {
|
||||
if err := Procedure(f); err != nil {
|
||||
return err
|
||||
}
|
||||
// Loop
|
||||
@ -234,63 +231,55 @@ func (s *FrontendQt) QtExecute(Procedure func(*FrontendQt) error) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *FrontendQt) openLogs() {
|
||||
go open.Run(s.config.GetLogDir())
|
||||
func (f *FrontendQt) openLogs() {
|
||||
go open.Run(f.config.GetLogDir())
|
||||
}
|
||||
|
||||
func (s *FrontendQt) openReport() {
|
||||
go open.Run(s.Qml.ImportLogFileName())
|
||||
func (f *FrontendQt) openReport() {
|
||||
go open.Run(f.Qml.ImportLogFileName())
|
||||
}
|
||||
|
||||
func (s *FrontendQt) openDownloadLink() {
|
||||
go open.Run(s.updates.GetDownloadLink())
|
||||
func (f *FrontendQt) openDownloadLink() {
|
||||
go open.Run(f.updates.GetDownloadLink())
|
||||
}
|
||||
|
||||
func (s *FrontendQt) sendImportReport(address, reportFile string) (isOK bool) {
|
||||
/*
|
||||
accname := "[No account logged in]"
|
||||
if s.Accounts.Count() > 0 {
|
||||
accname = s.Accounts.get(0).Account()
|
||||
}
|
||||
|
||||
basename := filepath.Base(reportFile)
|
||||
req := pmapi.ReportReq{
|
||||
OS: core.QSysInfo_ProductType(),
|
||||
OSVersion: core.QSysInfo_PrettyProductName(),
|
||||
Title: "[Import Export] Import report: " + basename,
|
||||
Description: "Sending import report file in attachment.",
|
||||
Username: accname,
|
||||
Email: address,
|
||||
}
|
||||
|
||||
report, err := os.Open(reportFile)
|
||||
if err != nil {
|
||||
log.Errorln("report file open:", err)
|
||||
isOK = false
|
||||
}
|
||||
req.AddAttachment("log", basename, report)
|
||||
|
||||
c := pmapi.NewClient(backend.APIConfig, "import_reporter")
|
||||
err = c.Report(req)
|
||||
if err != nil {
|
||||
log.Errorln("while sendReport:", err)
|
||||
isOK = false
|
||||
return
|
||||
}
|
||||
log.Infof("Report %q send successfully", basename)
|
||||
isOK = true
|
||||
*/
|
||||
return false
|
||||
}
|
||||
|
||||
// sendBug is almost idetical to bridge
|
||||
func (s *FrontendQt) sendBug(description, emailClient, address string) (isOK bool) {
|
||||
isOK = true
|
||||
// sendImportReport sends an anonymized import or export report file to our customer support
|
||||
func (f *FrontendQt) sendImportReport(address string) bool { // Todo_: Rename to sendReport?
|
||||
var accname = "No account logged in"
|
||||
if s.Accounts.Model.Count() > 0 {
|
||||
accname = s.Accounts.Model.Get(0).Account()
|
||||
if f.Accounts.Model.Count() > 0 {
|
||||
accname = f.Accounts.Model.Get(0).Account()
|
||||
}
|
||||
if err := s.ie.ReportBug(
|
||||
|
||||
if f.progress == nil {
|
||||
log.Errorln("Failed to send process report: Missing progress")
|
||||
return false
|
||||
}
|
||||
|
||||
report := f.progress.GenerateBugReport()
|
||||
|
||||
if err := f.ie.ReportFile(
|
||||
core.QSysInfo_ProductType(),
|
||||
core.QSysInfo_PrettyProductName(),
|
||||
accname,
|
||||
address,
|
||||
report,
|
||||
); err != nil {
|
||||
log.Errorln("Failed to send process report:", err)
|
||||
return false
|
||||
}
|
||||
|
||||
log.Info("Report send successfully")
|
||||
return true
|
||||
}
|
||||
|
||||
// sendBug sends a bug report described by user to our customer support
|
||||
func (f *FrontendQt) sendBug(description, emailClient, address string) bool {
|
||||
var accname = "No account logged in"
|
||||
if f.Accounts.Model.Count() > 0 {
|
||||
accname = f.Accounts.Model.Get(0).Account()
|
||||
}
|
||||
|
||||
if err := f.ie.ReportBug(
|
||||
core.QSysInfo_ProductType(),
|
||||
core.QSysInfo_PrettyProductName(),
|
||||
description,
|
||||
@ -299,41 +288,43 @@ func (s *FrontendQt) sendBug(description, emailClient, address string) (isOK boo
|
||||
emailClient,
|
||||
); err != nil {
|
||||
log.Errorln("while sendBug:", err)
|
||||
isOK = false
|
||||
return false
|
||||
}
|
||||
return
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// checkInternet is almost idetical to bridge
|
||||
func (s *FrontendQt) checkInternet() {
|
||||
s.Qml.SetConnectionStatus(s.ie.CheckConnection() == nil)
|
||||
func (f *FrontendQt) checkInternet() {
|
||||
f.Qml.SetConnectionStatus(f.ie.CheckConnection() == nil)
|
||||
}
|
||||
|
||||
func (s *FrontendQt) showError(err error) {
|
||||
code := 0 // TODO err.Code()
|
||||
s.Qml.SetErrorDescription(err.Error())
|
||||
func (f *FrontendQt) showError(code int, err error) {
|
||||
f.Qml.SetErrorDescription(err.Error())
|
||||
log.WithField("code", code).Errorln(err.Error())
|
||||
s.Qml.NotifyError(code)
|
||||
f.Qml.NotifyError(code)
|
||||
}
|
||||
|
||||
func (s *FrontendQt) emitEvent(evType, msg string) {
|
||||
s.eventListener.Emit(evType, msg)
|
||||
func (f *FrontendQt) emitEvent(evType, msg string) {
|
||||
f.eventListener.Emit(evType, msg)
|
||||
}
|
||||
|
||||
func (s *FrontendQt) setProgressManager(progress *transfer.Progress) {
|
||||
s.Qml.ConnectPauseProcess(func() { progress.Pause("user") })
|
||||
s.Qml.ConnectResumeProcess(progress.Resume)
|
||||
s.Qml.ConnectCancelProcess(func(clearUnfinished bool) {
|
||||
// TODO clear unfinished
|
||||
func (f *FrontendQt) setProgressManager(progress *transfer.Progress) {
|
||||
f.progress = progress
|
||||
f.ErrorList.Progress = progress
|
||||
|
||||
f.Qml.ConnectPauseProcess(func() { progress.Pause("paused") })
|
||||
f.Qml.ConnectResumeProcess(progress.Resume)
|
||||
f.Qml.ConnectCancelProcess(func() {
|
||||
progress.Stop()
|
||||
})
|
||||
|
||||
go func() {
|
||||
defer func() {
|
||||
s.Qml.DisconnectPauseProcess()
|
||||
s.Qml.DisconnectResumeProcess()
|
||||
s.Qml.DisconnectCancelProcess()
|
||||
s.Qml.SetProgress(1)
|
||||
f.Qml.DisconnectPauseProcess()
|
||||
f.Qml.DisconnectResumeProcess()
|
||||
f.Qml.DisconnectCancelProcess()
|
||||
f.Qml.SetProgress(1)
|
||||
}()
|
||||
|
||||
//TODO get log file (in old code it was here, but this is ugly place probably somewhere else)
|
||||
@ -344,119 +335,123 @@ func (s *FrontendQt) setProgressManager(progress *transfer.Progress) {
|
||||
}
|
||||
failed, imported, _, _, total := progress.GetCounts()
|
||||
if total != 0 { // udate total
|
||||
s.Qml.SetTotal(int(total))
|
||||
f.Qml.SetTotal(int(total))
|
||||
}
|
||||
s.Qml.SetProgressFails(int(failed))
|
||||
s.Qml.SetProgressDescription(progress.PauseReason()) // TODO add description when changing folders?
|
||||
f.Qml.SetProgressFails(int(failed))
|
||||
f.Qml.SetProgressDescription(progress.PauseReason()) // TODO add description when changing folders?
|
||||
if total > 0 {
|
||||
newProgress := float32(imported+failed) / float32(total)
|
||||
if newProgress >= 0 && newProgress != s.Qml.Progress() {
|
||||
s.Qml.SetProgress(newProgress)
|
||||
s.Qml.ProgressChanged(newProgress)
|
||||
if newProgress >= 0 && newProgress != f.Qml.Progress() {
|
||||
f.Qml.SetProgress(newProgress)
|
||||
f.Qml.ProgressChanged(newProgress)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO fatal error?
|
||||
if err := progress.GetFatalError(); err != nil {
|
||||
f.Qml.SetProgressDescription(err.Error())
|
||||
} else {
|
||||
f.Qml.SetProgressDescription("")
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// StartUpdate is identical to bridge
|
||||
func (s *FrontendQt) StartUpdate() {
|
||||
func (f *FrontendQt) StartUpdate() {
|
||||
progress := make(chan updates.Progress)
|
||||
go func() { // Update progress in QML.
|
||||
defer s.panicHandler.HandlePanic()
|
||||
defer f.panicHandler.HandlePanic()
|
||||
for current := range progress {
|
||||
s.Qml.SetProgress(current.Processed)
|
||||
s.Qml.SetProgressDescription(strconv.Itoa(current.Description))
|
||||
f.Qml.SetProgress(current.Processed)
|
||||
f.Qml.SetProgressDescription(strconv.Itoa(current.Description))
|
||||
// Error happend
|
||||
if current.Err != nil {
|
||||
log.Error("update progress: ", current.Err)
|
||||
s.Qml.UpdateFinished(true)
|
||||
f.Qml.UpdateFinished(true)
|
||||
return
|
||||
}
|
||||
// Finished everything OK.
|
||||
if current.Description >= updates.InfoQuitApp {
|
||||
s.Qml.UpdateFinished(false)
|
||||
f.Qml.UpdateFinished(false)
|
||||
time.Sleep(3 * time.Second) // Just notify.
|
||||
s.Qml.SetIsRestarting(current.Description == updates.InfoRestartApp)
|
||||
s.App.Quit()
|
||||
f.Qml.SetIsRestarting(current.Description == updates.InfoRestartApp)
|
||||
f.App.Quit()
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
go func() {
|
||||
defer s.panicHandler.HandlePanic()
|
||||
s.updates.StartUpgrade(progress)
|
||||
defer f.panicHandler.HandlePanic()
|
||||
f.updates.StartUpgrade(progress)
|
||||
}()
|
||||
}
|
||||
|
||||
// isNewVersionAvailable is identical to bridge
|
||||
// return 0 when local version is fine
|
||||
// return 1 when new version is available
|
||||
func (s *FrontendQt) isNewVersionAvailable(showMessage bool) {
|
||||
func (f *FrontendQt) isNewVersionAvailable(showMessage bool) {
|
||||
go func() {
|
||||
defer s.Qml.ProcessFinished()
|
||||
isUpToDate, latestVersionInfo, err := s.updates.CheckIsUpToDate()
|
||||
defer f.Qml.ProcessFinished()
|
||||
isUpToDate, latestVersionInfo, err := f.updates.CheckIsUpToDate()
|
||||
if err != nil {
|
||||
log.Warnln("Cannot retrieve version info: ", err)
|
||||
s.checkInternet()
|
||||
f.checkInternet()
|
||||
return
|
||||
}
|
||||
s.Qml.SetConnectionStatus(true) // if we are here connection is ok
|
||||
f.Qml.SetConnectionStatus(true) // if we are here connection is ok
|
||||
if isUpToDate {
|
||||
s.Qml.SetUpdateState(StatusUpToDate)
|
||||
f.Qml.SetUpdateState(StatusUpToDate)
|
||||
if showMessage {
|
||||
s.Qml.NotifyVersionIsTheLatest()
|
||||
f.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.SetUpdateState(StatusNewVersionAvailable)
|
||||
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)
|
||||
}()
|
||||
}
|
||||
|
||||
func (s *FrontendQt) resetSource() {
|
||||
if s.transfer != nil {
|
||||
s.transfer.ResetRules()
|
||||
if err := s.loadStructuresForImport(); err != nil {
|
||||
func (f *FrontendQt) resetSource() {
|
||||
if f.transfer != nil {
|
||||
f.transfer.ResetRules()
|
||||
if err := f.loadStructuresForImport(); err != nil {
|
||||
log.WithError(err).Error("Cannot reload structures after reseting rules.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// getLocalVersionInfo is identical to bridge.
|
||||
func (s *FrontendQt) getLocalVersionInfo() {
|
||||
defer s.Qml.ProcessFinished()
|
||||
localVersion := s.updates.GetLocalVersion()
|
||||
s.Qml.SetNewversion(localVersion.Version)
|
||||
s.Qml.SetChangelog(localVersion.ReleaseNotes)
|
||||
s.Qml.SetBugfixes(localVersion.ReleaseFixedBugs)
|
||||
func (f *FrontendQt) getLocalVersionInfo() {
|
||||
defer f.Qml.ProcessFinished()
|
||||
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.
|
||||
func (s *FrontendQt) leastUsedColor() string {
|
||||
if s.transfer == nil {
|
||||
func (f *FrontendQt) leastUsedColor() string {
|
||||
if f.transfer == nil {
|
||||
log.Errorln("Getting least used color before transfer exist.")
|
||||
return "#7272a7"
|
||||
}
|
||||
|
||||
m, err := s.transfer.TargetMailboxes()
|
||||
m, err := f.transfer.TargetMailboxes()
|
||||
|
||||
if err != nil {
|
||||
log.Errorln("Getting least used color:", err)
|
||||
s.showError(err)
|
||||
f.showError(errUnknownError, err)
|
||||
}
|
||||
|
||||
return transfer.LeastUsedColor(m)
|
||||
}
|
||||
|
||||
// createLabelOrFolder performs an IE target mailbox creation.
|
||||
func (s *FrontendQt) createLabelOrFolder(email, name, color string, isLabel bool, sourceID string) bool {
|
||||
func (f *FrontendQt) createLabelOrFolder(email, name, color string, isLabel bool, sourceID string) bool {
|
||||
// Prepare new mailbox.
|
||||
m := transfer.Mailbox{
|
||||
Name: name,
|
||||
@ -466,32 +461,28 @@ func (s *FrontendQt) createLabelOrFolder(email, name, color string, isLabel bool
|
||||
|
||||
// Select least used color if no color given.
|
||||
if m.Color == "" {
|
||||
m.Color = s.leastUsedColor()
|
||||
m.Color = f.leastUsedColor()
|
||||
}
|
||||
|
||||
f.TransferRules.BeginResetModel()
|
||||
defer f.TransferRules.EndResetModel()
|
||||
|
||||
// Create mailbox.
|
||||
newLabel, err := s.transfer.CreateTargetMailbox(m)
|
||||
|
||||
m, err := f.transfer.CreateTargetMailbox(m)
|
||||
if err != nil {
|
||||
log.Errorln("Folder/Label creating:", err)
|
||||
s.showError(err)
|
||||
return false
|
||||
}
|
||||
|
||||
// TODO: notify UI of newly added folders/labels
|
||||
/*errc := s.PMStructure.Load(email, false)
|
||||
if errc != nil {
|
||||
s.showError(errc)
|
||||
return false
|
||||
}*/
|
||||
|
||||
if sourceID != "" {
|
||||
if isLabel {
|
||||
s.ExternalStructure.addTargetLabelID(sourceID, newLabel.ID)
|
||||
f.showError(errCreateLabelFailed, err)
|
||||
} else {
|
||||
s.ExternalStructure.setTargetFolderID(sourceID, newLabel.ID)
|
||||
f.showError(errCreateFolderFailed, err)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
if sourceID == "-1" {
|
||||
f.transfer.SetGlobalMailbox(&m)
|
||||
} else {
|
||||
f.TransferRules.addTargetID(sourceID, m.Hash())
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@ -19,14 +19,19 @@
|
||||
|
||||
package qtie
|
||||
|
||||
import "github.com/ProtonMail/proton-bridge/internal/transfer"
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/internal/transfer"
|
||||
)
|
||||
|
||||
// wrapper for QML
|
||||
func (f *FrontendQt) setupAndLoadForImport(isFromIMAP bool, sourcePath, sourceEmail, sourcePassword, sourceServer, sourcePort, targetAddress string) {
|
||||
errCode := errUnknownError
|
||||
var err error
|
||||
defer func() {
|
||||
if err != nil {
|
||||
f.showError(err)
|
||||
f.showError(errCode, err)
|
||||
f.Qml.ImportStructuresLoadFinished(false)
|
||||
} else {
|
||||
f.Qml.ImportStructuresLoadFinished(true)
|
||||
@ -36,11 +41,23 @@ func (f *FrontendQt) setupAndLoadForImport(isFromIMAP bool, sourcePath, sourceEm
|
||||
if isFromIMAP {
|
||||
f.transfer, err = f.ie.GetRemoteImporter(targetAddress, sourceEmail, sourcePassword, sourceServer, sourcePort)
|
||||
if err != nil {
|
||||
switch {
|
||||
case errors.Is(err, &transfer.ErrIMAPConnection{}):
|
||||
errCode = errWrongServerPathOrPort
|
||||
case errors.Is(err, &transfer.ErrIMAPAuth{}):
|
||||
errCode = errWrongLoginOrPassword
|
||||
case errors.Is(err, &transfer.ErrIMAPAuthMethod{}):
|
||||
errCode = errWrongAuthMethod
|
||||
default:
|
||||
errCode = errRemoteSourceLoadFailed
|
||||
}
|
||||
return
|
||||
}
|
||||
} else {
|
||||
f.transfer, err = f.ie.GetLocalImporter(targetAddress, sourcePath)
|
||||
if err != nil {
|
||||
// The only error can be problem to load PM user and address.
|
||||
errCode = errPMLoadFailed
|
||||
return
|
||||
}
|
||||
}
|
||||
@ -51,27 +68,7 @@ func (f *FrontendQt) setupAndLoadForImport(isFromIMAP bool, sourcePath, sourceEm
|
||||
}
|
||||
|
||||
func (f *FrontendQt) loadStructuresForImport() error {
|
||||
f.PMStructure.Clear()
|
||||
targetMboxes, err := f.transfer.TargetMailboxes()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, mbox := range targetMboxes {
|
||||
rule := &transfer.Rule{}
|
||||
f.PMStructure.addEntry(newFolderInfo(mbox, rule))
|
||||
}
|
||||
|
||||
f.ExternalStructure.Clear()
|
||||
sourceMboxes, err := f.transfer.SourceMailboxes()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, mbox := range sourceMboxes {
|
||||
rule := f.transfer.GetRule(mbox)
|
||||
f.ExternalStructure.addEntry(newFolderInfo(mbox, rule))
|
||||
}
|
||||
|
||||
f.ExternalStructure.transfer = f.transfer
|
||||
f.TransferRules.setTransfer(f.transfer)
|
||||
|
||||
return nil
|
||||
}
|
||||
@ -82,8 +79,9 @@ func (f *FrontendQt) StartImport(email string) { // TODO email not needed
|
||||
f.Qml.SetProgress(0.0)
|
||||
f.Qml.SetTotal(1)
|
||||
f.Qml.SetImportLogFileName("")
|
||||
f.ErrorList.Clear()
|
||||
|
||||
progress := f.transfer.Start()
|
||||
|
||||
f.Qml.SetImportLogFileName(progress.FileReport())
|
||||
f.setProgressManager(progress)
|
||||
}
|
||||
|
||||
188
internal/frontend/qt-ie/mbox.go
Normal file
188
internal/frontend/qt-ie/mbox.go
Normal file
@ -0,0 +1,188 @@
|
||||
// Copyright (c) 2020 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/>.
|
||||
|
||||
// +build !nogui
|
||||
|
||||
package qtie
|
||||
|
||||
import (
|
||||
qtcommon "github.com/ProtonMail/proton-bridge/internal/frontend/qt-common"
|
||||
"github.com/ProtonMail/proton-bridge/internal/transfer"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/therecipe/qt/core"
|
||||
)
|
||||
|
||||
// MboxList is an interface between QML and targets for given rule.
|
||||
type MboxList struct {
|
||||
core.QAbstractListModel
|
||||
|
||||
containsFolders bool // Provides only folders if true. On the other hand provides only labels if false
|
||||
transfer *transfer.Transfer
|
||||
rule *transfer.Rule
|
||||
log *logrus.Entry
|
||||
|
||||
_ int `property:"selectedIndex"`
|
||||
|
||||
_ func() `constructor:"init"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
// This is needed so the type exists in QML files.
|
||||
MboxList_QRegisterMetaType()
|
||||
}
|
||||
|
||||
func newMboxList(t *TransferRules, rule *transfer.Rule, containsFolders bool) *MboxList {
|
||||
m := NewMboxList(t)
|
||||
m.BeginResetModel()
|
||||
m.transfer = t.transfer
|
||||
m.rule = rule
|
||||
m.containsFolders = containsFolders
|
||||
m.log = log.
|
||||
WithField("rule", m.rule.SourceMailbox.Hash()).
|
||||
WithField("folders", m.containsFolders)
|
||||
m.EndResetModel()
|
||||
m.itemsChanged(rule)
|
||||
return m
|
||||
}
|
||||
|
||||
func (m *MboxList) init() {
|
||||
m.ConnectRowCount(m.rowCount)
|
||||
m.ConnectRoleNames(m.roleNames)
|
||||
m.ConnectData(m.data)
|
||||
}
|
||||
|
||||
func (m *MboxList) rowCount(index *core.QModelIndex) int {
|
||||
return len(m.targetMailboxes())
|
||||
}
|
||||
|
||||
func (m *MboxList) roleNames() map[int]*core.QByteArray {
|
||||
m.log.
|
||||
WithField("isActive", MboxIsActive).
|
||||
WithField("id", MboxID).
|
||||
WithField("color", MboxColor).
|
||||
Debug("role names")
|
||||
return map[int]*core.QByteArray{
|
||||
MboxIsActive: qtcommon.NewQByteArrayFromString("isActive"),
|
||||
MboxID: qtcommon.NewQByteArrayFromString("mboxID"),
|
||||
MboxName: qtcommon.NewQByteArrayFromString("name"),
|
||||
MboxType: qtcommon.NewQByteArrayFromString("type"),
|
||||
MboxColor: qtcommon.NewQByteArrayFromString("iconColor"),
|
||||
}
|
||||
}
|
||||
|
||||
func (m *MboxList) data(index *core.QModelIndex, role int) *core.QVariant {
|
||||
allTargets := m.targetMailboxes()
|
||||
|
||||
i, valid := index.Row(), index.IsValid()
|
||||
l := m.log.WithField("row", i).WithField("role", role)
|
||||
l.Trace("called data()")
|
||||
|
||||
if !valid || i >= len(allTargets) {
|
||||
l.WithField("row", i).Warning("Invalid index")
|
||||
return core.NewQVariant()
|
||||
}
|
||||
|
||||
if m.transfer == nil {
|
||||
l.Warning("Requested mbox list data before transfer is connected")
|
||||
return qtcommon.NewQVariantString("")
|
||||
}
|
||||
|
||||
mbox := allTargets[i]
|
||||
|
||||
switch role {
|
||||
|
||||
case MboxIsActive:
|
||||
for _, selectedMailbox := range m.rule.TargetMailboxes {
|
||||
if selectedMailbox.Hash() == mbox.Hash() {
|
||||
return qtcommon.NewQVariantBool(true)
|
||||
}
|
||||
}
|
||||
return qtcommon.NewQVariantBool(false)
|
||||
|
||||
case MboxID:
|
||||
return qtcommon.NewQVariantString(mbox.Hash())
|
||||
|
||||
case MboxName, int(core.Qt__DisplayRole):
|
||||
return qtcommon.NewQVariantString(mbox.Name)
|
||||
|
||||
case MboxType:
|
||||
t := "label"
|
||||
if mbox.IsExclusive {
|
||||
t = "folder"
|
||||
}
|
||||
return qtcommon.NewQVariantString(t)
|
||||
|
||||
case MboxColor:
|
||||
return qtcommon.NewQVariantString(mbox.Color)
|
||||
|
||||
default:
|
||||
l.Error("Requested mbox list data with unknown role")
|
||||
return qtcommon.NewQVariantString("")
|
||||
}
|
||||
}
|
||||
|
||||
func (m *MboxList) targetMailboxes() []transfer.Mailbox {
|
||||
if m.transfer == nil {
|
||||
m.log.Warning("Requested target mailboxes before transfer is connected")
|
||||
}
|
||||
|
||||
mailboxes, err := m.transfer.TargetMailboxes()
|
||||
if err != nil {
|
||||
m.log.WithError(err).Error("Unable to get target mailboxes")
|
||||
}
|
||||
|
||||
return m.filter(mailboxes)
|
||||
}
|
||||
|
||||
func (m *MboxList) filter(mailboxes []transfer.Mailbox) (filtered []transfer.Mailbox) {
|
||||
for _, mailbox := range mailboxes {
|
||||
if mailbox.IsExclusive == m.containsFolders {
|
||||
filtered = append(filtered, mailbox)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (m *MboxList) itemsChanged(rule *transfer.Rule) {
|
||||
m.rule = rule
|
||||
allTargets := m.targetMailboxes()
|
||||
l := m.log.WithField("count", len(allTargets))
|
||||
l.Trace("called itemChanged()")
|
||||
defer func() {
|
||||
l.WithField("selected", m.SelectedIndex()).Trace("index updated")
|
||||
}()
|
||||
|
||||
// NOTE: Be careful with indices: If they are invalid the DataChanged
|
||||
// signal will not be sent to QML e.g. `end == rowCount - 1`
|
||||
if len(allTargets) > 0 {
|
||||
begin := m.Index(0, 0, core.NewQModelIndex())
|
||||
end := m.Index(len(allTargets)-1, 0, core.NewQModelIndex())
|
||||
changedRoles := []int{MboxIsActive}
|
||||
m.DataChanged(begin, end, changedRoles)
|
||||
}
|
||||
|
||||
for index, targetMailbox := range allTargets {
|
||||
for _, selectedTarget := range m.rule.TargetMailboxes {
|
||||
if targetMailbox.Hash() == selectedTarget.Hash() {
|
||||
m.SetSelectedIndex(index)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
m.SetSelectedIndex(-1)
|
||||
}
|
||||
377
internal/frontend/qt-ie/transfer_rules.go
Normal file
377
internal/frontend/qt-ie/transfer_rules.go
Normal file
@ -0,0 +1,377 @@
|
||||
// Copyright (c) 2020 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/>.
|
||||
|
||||
// +build !nogui
|
||||
|
||||
package qtie
|
||||
|
||||
import (
|
||||
qtcommon "github.com/ProtonMail/proton-bridge/internal/frontend/qt-common"
|
||||
"github.com/ProtonMail/proton-bridge/internal/transfer"
|
||||
"github.com/therecipe/qt/core"
|
||||
)
|
||||
|
||||
// TransferRules is an interface between QML and transfer.
|
||||
type TransferRules struct {
|
||||
core.QAbstractListModel
|
||||
|
||||
transfer *transfer.Transfer
|
||||
|
||||
targetFoldersCache map[string]*MboxList
|
||||
targetLabelsCache map[string]*MboxList
|
||||
|
||||
_ func() `constructor:"init"`
|
||||
|
||||
_ func(sourceID string) *MboxList `slot:"targetFolders,auto"`
|
||||
_ func(sourceID string) *MboxList `slot:"targetLabels,auto"`
|
||||
_ func(sourceID string, isActive bool) `slot:"setIsRuleActive,auto"`
|
||||
_ func(groupName string, isActive bool) `slot:"setIsGroupActive,auto"`
|
||||
_ func(sourceID string, fromDate int64, toDate int64) `slot:"setFromToDate,auto"`
|
||||
_ func(sourceID string, targetID string) `slot:"addTargetID,auto"`
|
||||
_ func(sourceID string, targetID string) `slot:"removeTargetID,auto"`
|
||||
|
||||
_ int `property:"globalFromDate"`
|
||||
_ int `property:"globalToDate"`
|
||||
_ bool `property:"isLabelGroupSelected"`
|
||||
_ bool `property:"isFolderGroupSelected"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
// This is needed so the type exists in QML files.
|
||||
TransferRules_QRegisterMetaType()
|
||||
}
|
||||
|
||||
func (t *TransferRules) init() {
|
||||
log.Trace("Initializing transfer rules")
|
||||
|
||||
t.targetFoldersCache = make(map[string]*MboxList)
|
||||
t.targetLabelsCache = make(map[string]*MboxList)
|
||||
|
||||
t.SetGlobalFromDate(0)
|
||||
t.SetGlobalToDate(0)
|
||||
|
||||
t.ConnectRowCount(t.rowCount)
|
||||
t.ConnectRoleNames(t.roleNames)
|
||||
t.ConnectData(t.data)
|
||||
}
|
||||
|
||||
func (t *TransferRules) rowCount(index *core.QModelIndex) int {
|
||||
if t.transfer == nil {
|
||||
return 0
|
||||
}
|
||||
return len(t.transfer.GetRules())
|
||||
}
|
||||
|
||||
func (t *TransferRules) roleNames() map[int]*core.QByteArray {
|
||||
return map[int]*core.QByteArray{
|
||||
MboxIsActive: qtcommon.NewQByteArrayFromString("isActive"),
|
||||
MboxID: qtcommon.NewQByteArrayFromString("mboxID"),
|
||||
MboxName: qtcommon.NewQByteArrayFromString("name"),
|
||||
MboxType: qtcommon.NewQByteArrayFromString("type"),
|
||||
MboxColor: qtcommon.NewQByteArrayFromString("iconColor"),
|
||||
RuleTargetLabelColors: qtcommon.NewQByteArrayFromString("labelColors"),
|
||||
RuleFromDate: qtcommon.NewQByteArrayFromString("fromDate"),
|
||||
RuleToDate: qtcommon.NewQByteArrayFromString("toDate"),
|
||||
}
|
||||
}
|
||||
|
||||
func (t *TransferRules) data(index *core.QModelIndex, role int) *core.QVariant {
|
||||
i, valid := index.Row(), index.IsValid()
|
||||
|
||||
if !valid || i >= t.rowCount(index) {
|
||||
log.WithField("row", i).Warning("Invalid index")
|
||||
return core.NewQVariant()
|
||||
}
|
||||
|
||||
log := log.WithField("row", i).WithField("role", role)
|
||||
|
||||
if t.transfer == nil {
|
||||
log.Warning("Requested transfer rules data before transfer is connected")
|
||||
return qtcommon.NewQVariantString("")
|
||||
}
|
||||
|
||||
rule := t.transfer.GetRules()[i]
|
||||
|
||||
switch role {
|
||||
case MboxIsActive:
|
||||
return qtcommon.NewQVariantBool(rule.Active)
|
||||
|
||||
case MboxID:
|
||||
return qtcommon.NewQVariantString(rule.SourceMailbox.Hash())
|
||||
|
||||
case MboxName:
|
||||
return qtcommon.NewQVariantString(rule.SourceMailbox.Name)
|
||||
|
||||
case MboxType:
|
||||
if rule.SourceMailbox.IsSystemFolder() {
|
||||
return qtcommon.NewQVariantString(FolderTypeSystem)
|
||||
}
|
||||
if rule.SourceMailbox.IsExclusive {
|
||||
return qtcommon.NewQVariantString(FolderTypeFolder)
|
||||
}
|
||||
return qtcommon.NewQVariantString(FolderTypeLabel)
|
||||
|
||||
case MboxColor:
|
||||
return qtcommon.NewQVariantString(rule.SourceMailbox.Color)
|
||||
|
||||
case RuleTargetLabelColors:
|
||||
colors := ""
|
||||
for _, m := range rule.TargetMailboxes {
|
||||
if m.IsExclusive {
|
||||
continue
|
||||
}
|
||||
if colors != "" {
|
||||
colors += ";"
|
||||
}
|
||||
colors += m.Color
|
||||
}
|
||||
return qtcommon.NewQVariantString(colors)
|
||||
|
||||
case RuleFromDate:
|
||||
return qtcommon.NewQVariantLong(rule.FromTime)
|
||||
|
||||
case RuleToDate:
|
||||
return qtcommon.NewQVariantLong(rule.ToTime)
|
||||
|
||||
default:
|
||||
log.Error("Requested transfer rules data with unknown role")
|
||||
return qtcommon.NewQVariantString("")
|
||||
}
|
||||
}
|
||||
|
||||
func (t *TransferRules) setTransfer(transfer *transfer.Transfer) {
|
||||
log.Debug("Setting transfer")
|
||||
t.BeginResetModel()
|
||||
defer t.EndResetModel()
|
||||
|
||||
t.transfer = transfer
|
||||
|
||||
t.updateGroupSelection()
|
||||
}
|
||||
|
||||
// Getters
|
||||
|
||||
func (t *TransferRules) targetFolders(sourceID string) *MboxList {
|
||||
rule := t.getRule(sourceID)
|
||||
if rule == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if t.targetFoldersCache[sourceID] == nil {
|
||||
log.WithField("source", sourceID).Debug("New target folder")
|
||||
t.targetFoldersCache[sourceID] = newMboxList(t, rule, true)
|
||||
}
|
||||
|
||||
return t.targetFoldersCache[sourceID]
|
||||
}
|
||||
|
||||
func (t *TransferRules) targetLabels(sourceID string) *MboxList {
|
||||
rule := t.getRule(sourceID)
|
||||
if rule == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if t.targetLabelsCache[sourceID] == nil {
|
||||
log.WithField("source", sourceID).Debug("New target label")
|
||||
t.targetLabelsCache[sourceID] = newMboxList(t, rule, false)
|
||||
}
|
||||
|
||||
return t.targetLabelsCache[sourceID]
|
||||
}
|
||||
|
||||
// Setters
|
||||
|
||||
func (t *TransferRules) setIsGroupActive(groupName string, isActive bool) {
|
||||
wantExclusive := (groupName == FolderTypeLabel)
|
||||
for _, rule := range t.transfer.GetRules() {
|
||||
if rule.SourceMailbox.IsExclusive != wantExclusive {
|
||||
continue
|
||||
}
|
||||
if rule.SourceMailbox.IsSystemFolder() {
|
||||
continue
|
||||
}
|
||||
if rule.Active != isActive {
|
||||
t.setIsRuleActive(rule.SourceMailbox.Hash(), isActive)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *TransferRules) setIsRuleActive(sourceID string, isActive bool) {
|
||||
log.WithField("source", sourceID).WithField("active", isActive).Trace("Setting rule as active/inactive")
|
||||
|
||||
rule := t.getRule(sourceID)
|
||||
if rule == nil {
|
||||
return
|
||||
}
|
||||
if isActive {
|
||||
t.setRule(rule.SourceMailbox, rule.TargetMailboxes, rule.FromTime, rule.ToTime, []int{MboxIsActive})
|
||||
} else {
|
||||
t.unsetRule(rule.SourceMailbox)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *TransferRules) setFromToDate(sourceID string, fromTime int64, toTime int64) {
|
||||
log.WithField("source", sourceID).WithField("fromTime", fromTime).WithField("toTime", toTime).Trace("Setting from and to dates")
|
||||
|
||||
if sourceID == "-1" {
|
||||
t.transfer.SetGlobalTimeLimit(fromTime, toTime)
|
||||
return
|
||||
}
|
||||
|
||||
rule := t.getRule(sourceID)
|
||||
if rule == nil {
|
||||
return
|
||||
}
|
||||
t.setRule(rule.SourceMailbox, rule.TargetMailboxes, fromTime, toTime, []int{RuleFromDate, RuleToDate})
|
||||
}
|
||||
|
||||
func (t *TransferRules) addTargetID(sourceID string, targetID string) {
|
||||
log.WithField("source", sourceID).WithField("target", targetID).Trace("Adding target")
|
||||
|
||||
rule := t.getRule(sourceID)
|
||||
if rule == nil {
|
||||
return
|
||||
}
|
||||
targetMailboxToAdd := t.getMailbox(t.transfer.TargetMailboxes, targetID)
|
||||
if targetMailboxToAdd == nil {
|
||||
return
|
||||
}
|
||||
|
||||
newTargetMailboxes := []transfer.Mailbox{}
|
||||
found := false
|
||||
for _, targetMailbox := range rule.TargetMailboxes {
|
||||
if targetMailbox.Hash() == targetMailboxToAdd.Hash() {
|
||||
found = true
|
||||
}
|
||||
if !targetMailboxToAdd.IsExclusive || (targetMailboxToAdd.IsExclusive && !targetMailbox.IsExclusive) {
|
||||
newTargetMailboxes = append(newTargetMailboxes, targetMailbox)
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
newTargetMailboxes = append(newTargetMailboxes, *targetMailboxToAdd)
|
||||
}
|
||||
t.setRule(rule.SourceMailbox, newTargetMailboxes, rule.FromTime, rule.ToTime, []int{RuleTargetLabelColors})
|
||||
}
|
||||
|
||||
func (t *TransferRules) removeTargetID(sourceID string, targetID string) {
|
||||
log.WithField("source", sourceID).WithField("target", targetID).Trace("Removing target")
|
||||
|
||||
rule := t.getRule(sourceID)
|
||||
if rule == nil {
|
||||
return
|
||||
}
|
||||
targetMailboxToRemove := t.getMailbox(t.transfer.TargetMailboxes, targetID)
|
||||
if targetMailboxToRemove == nil {
|
||||
return
|
||||
}
|
||||
|
||||
newTargetMailboxes := []transfer.Mailbox{}
|
||||
for _, targetMailbox := range rule.TargetMailboxes {
|
||||
if targetMailbox.Hash() != targetMailboxToRemove.Hash() {
|
||||
newTargetMailboxes = append(newTargetMailboxes, targetMailbox)
|
||||
}
|
||||
}
|
||||
t.setRule(rule.SourceMailbox, newTargetMailboxes, rule.FromTime, rule.ToTime, []int{RuleTargetLabelColors})
|
||||
}
|
||||
|
||||
// Helpers
|
||||
|
||||
func (t *TransferRules) getRule(sourceID string) *transfer.Rule {
|
||||
mailbox := t.getMailbox(t.transfer.SourceMailboxes, sourceID)
|
||||
if mailbox == nil {
|
||||
return nil
|
||||
}
|
||||
return t.transfer.GetRule(*mailbox)
|
||||
}
|
||||
|
||||
func (t *TransferRules) getMailbox(mailboxesGetter func() ([]transfer.Mailbox, error), sourceID string) *transfer.Mailbox {
|
||||
if t.transfer == nil {
|
||||
log.Warn("Getting mailbox without avaiable transfer")
|
||||
return nil
|
||||
}
|
||||
|
||||
mailboxes, err := mailboxesGetter()
|
||||
if err != nil {
|
||||
log.WithError(err).Error("Failed to get source mailboxes")
|
||||
return nil
|
||||
}
|
||||
for _, mailbox := range mailboxes {
|
||||
if mailbox.Hash() == sourceID {
|
||||
return &mailbox
|
||||
}
|
||||
}
|
||||
log.WithField("source", sourceID).Error("Mailbox not found for source")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *TransferRules) setRule(sourceMailbox transfer.Mailbox, targetMailboxes []transfer.Mailbox, fromTime, toTime int64, changedRoles []int) {
|
||||
if err := t.transfer.SetRule(sourceMailbox, targetMailboxes, fromTime, toTime); err != nil {
|
||||
log.WithError(err).WithField("source", sourceMailbox.Hash()).Error("Failed to set rule")
|
||||
}
|
||||
t.ruleChanged(sourceMailbox, changedRoles)
|
||||
}
|
||||
|
||||
func (t *TransferRules) unsetRule(sourceMailbox transfer.Mailbox) {
|
||||
t.transfer.UnsetRule(sourceMailbox)
|
||||
t.ruleChanged(sourceMailbox, []int{MboxIsActive})
|
||||
}
|
||||
|
||||
func (t *TransferRules) ruleChanged(sourceMailbox transfer.Mailbox, changedRoles []int) {
|
||||
for row, rule := range t.transfer.GetRules() {
|
||||
if rule.SourceMailbox.Hash() != sourceMailbox.Hash() {
|
||||
continue
|
||||
}
|
||||
|
||||
t.targetFolders(sourceMailbox.Hash()).itemsChanged(rule)
|
||||
t.targetLabels(sourceMailbox.Hash()).itemsChanged(rule)
|
||||
|
||||
index := t.Index(row, 0, core.NewQModelIndex())
|
||||
if !index.IsValid() || row >= t.rowCount(index) {
|
||||
log.WithField("row", row).Warning("Invalid index")
|
||||
return
|
||||
}
|
||||
|
||||
t.DataChanged(index, index, changedRoles)
|
||||
break
|
||||
}
|
||||
|
||||
t.updateGroupSelection()
|
||||
}
|
||||
|
||||
func (t *TransferRules) updateGroupSelection() {
|
||||
areAllLabelsSelected, areAllFoldersSelected := true, true
|
||||
for _, rule := range t.transfer.GetRules() {
|
||||
if rule.Active {
|
||||
continue
|
||||
}
|
||||
if rule.SourceMailbox.IsSystemFolder() {
|
||||
continue
|
||||
}
|
||||
if rule.SourceMailbox.IsExclusive {
|
||||
areAllFoldersSelected = false
|
||||
} else {
|
||||
areAllLabelsSelected = false
|
||||
}
|
||||
|
||||
if !areAllLabelsSelected && !areAllFoldersSelected {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
t.SetIsLabelGroupSelected(areAllLabelsSelected)
|
||||
t.SetIsFolderGroupSelected(areAllFoldersSelected)
|
||||
}
|
||||
@ -71,7 +71,7 @@ type GoQMLInterface struct {
|
||||
_ func() `signal:"openManual"`
|
||||
_ func(showMessage bool) `signal:"runCheckVersion"`
|
||||
_ func() `slot:"getLocalVersionInfo"`
|
||||
_ func(fname string) `slot:"loadImportReports"`
|
||||
_ func() `slot:"loadImportReports"`
|
||||
|
||||
_ func() `slot:"quit"`
|
||||
_ func() `slot:"loadAccounts"`
|
||||
@ -87,7 +87,7 @@ type GoQMLInterface struct {
|
||||
_ func() string `slot:"getBackendVersion"`
|
||||
|
||||
_ func(description, client, address string) bool `slot:"sendBug"`
|
||||
_ func(address, fname string) bool `slot:"sendImportReport"`
|
||||
_ func(address string) bool `slot:"sendImportReport"`
|
||||
_ func(address string) `slot:"loadStructureForExport"`
|
||||
_ func() string `slot:"leastUsedColor"`
|
||||
_ func(username string, name string, color string, isLabel bool, sourceID string) bool `slot:"createLabelOrFolder"`
|
||||
@ -104,13 +104,13 @@ type GoQMLInterface struct {
|
||||
_ func(evType string, msg string) `signal:"emitEvent"`
|
||||
_ func(tabIndex int, message string) `signal:"notifyBubble"`
|
||||
|
||||
_ func() `signal:"bubbleClosed"`
|
||||
_ func() `signal:"simpleErrorHappen"`
|
||||
_ func() `signal:"askErrorHappen"`
|
||||
_ func() `signal:"retryErrorHappen"`
|
||||
_ func() `signal:"pauseProcess"`
|
||||
_ func() `signal:"resumeProcess"`
|
||||
_ func(clearUnfinished bool) `signal:"cancelProcess"`
|
||||
_ func() `signal:"bubbleClosed"`
|
||||
_ func() `signal:"simpleErrorHappen"`
|
||||
_ func() `signal:"askErrorHappen"`
|
||||
_ func() `signal:"retryErrorHappen"`
|
||||
_ func() `signal:"pauseProcess"`
|
||||
_ func() `signal:"resumeProcess"`
|
||||
_ func() `signal:"cancelProcess"`
|
||||
|
||||
_ func(iAccount int, prefRem bool) `slot:"deleteAccount"`
|
||||
_ func(iAccount int) `slot:"logoutAccount"`
|
||||
|
||||
Reference in New Issue
Block a user