Import/Export GUI

This commit is contained in:
Pavel Škoda
2020-06-23 15:35:54 +02:00
committed by Michal Horejsek
parent 1c10cc5065
commit 7e5e3d3dd4
50 changed files with 1793 additions and 692 deletions

View File

@ -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
)

View File

@ -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()
}

View File

@ -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) {

View File

@ -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
}

View File

@ -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)
}

View 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)
}

View 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)
}

View File

@ -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"`