proton-bridge/internal/frontend/qt-ie/folder_functions.go

532 lines
14 KiB
Go

// 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 (
"errors"
"strings"
qtcommon "github.com/ProtonMail/proton-bridge/internal/frontend/qt-common"
"github.com/ProtonMail/proton-bridge/internal/transfer"
"github.com/ProtonMail/proton-bridge/pkg/pmapi"
"github.com/therecipe/qt/core"
)
const (
GlobalOptionIndex = -1
)
var AllFolderInfoRoles = []int{
FolderId,
FolderName,
FolderColor,
FolderType,
FolderEntries,
IsFolderSelected,
FolderFromDate,
FolderToDate,
TargetFolderID,
TargetLabelIDs,
}
func getTargetHashes(mboxes []transfer.Mailbox) (targetFolderID, targetLabelIDs string) {
for _, targetMailbox := range mboxes {
if targetMailbox.IsExclusive {
targetFolderID = targetMailbox.Hash()
} else {
targetLabelIDs += targetMailbox.Hash() + ";"
}
}
targetLabelIDs = strings.Trim(targetLabelIDs, ";")
return
}
func newFolderInfo(mbox transfer.Mailbox, rule *transfer.Rule) *FolderInfo {
targetFolderID, targetLabelIDs := getTargetHashes(rule.TargetMailboxes)
entry := &FolderInfo{
mailbox: mbox,
FolderEntries: 1,
FromDate: rule.FromTime,
ToDate: rule.ToTime,
IsFolderSelected: rule.Active,
TargetFolderID: targetFolderID,
TargetLabelIDs: targetLabelIDs,
}
entry.FolderType = FolderTypeSystem
if !pmapi.IsSystemLabel(mbox.ID) {
if mbox.IsExclusive {
entry.FolderType = FolderTypeFolder
} else {
entry.FolderType = FolderTypeLabel
}
}
return entry
}
func (s *FolderStructure) saveRule(info *FolderInfo) error {
if s.transfer == nil {
return errors.New("missing transfer")
}
sourceMbox := info.mailbox
if !info.IsFolderSelected {
s.transfer.UnsetRule(sourceMbox)
return nil
}
allTargetMboxes, err := s.transfer.TargetMailboxes()
if err != nil {
return err
}
var targetMboxes []transfer.Mailbox
for _, target := range allTargetMboxes {
targetHash := target.Hash()
if info.TargetFolderID == targetHash || strings.Contains(info.TargetLabelIDs, targetHash) {
targetMboxes = append(targetMboxes, target)
}
}
return s.transfer.SetRule(sourceMbox, targetMboxes, info.FromDate, info.ToDate)
}
func (s *FolderInfo) updateTargetLabelIDs(targetLabelsSet map[string]struct{}) {
targets := []string{}
for key := range targetLabelsSet {
targets = append(targets, key)
}
s.TargetLabelIDs = strings.Join(targets, ";")
}
func (s *FolderInfo) AddTargetLabel(targetID string) {
if targetID == "" {
return
}
targetLabelsSet := s.getSetOfLabels()
targetLabelsSet[targetID] = struct{}{}
s.updateTargetLabelIDs(targetLabelsSet)
}
func (s *FolderInfo) RemoveTargetLabel(targetID string) {
if targetID == "" {
return
}
targetLabelsSet := s.getSetOfLabels()
delete(targetLabelsSet, targetID)
s.updateTargetLabelIDs(targetLabelsSet)
}
func (s *FolderInfo) IsType(askType string) bool {
return s.FolderType == askType
}
func (s *FolderInfo) getSetOfLabels() (uniqSet map[string]struct{}) {
uniqSet = make(map[string]struct{})
for _, label := range s.TargetLabelIDList() {
uniqSet[label] = struct{}{}
}
return
}
func (s *FolderInfo) TargetLabelIDList() []string {
return strings.FieldsFunc(
s.TargetLabelIDs,
func(c rune) bool { return c == ';' },
)
}
// Get data
func (s *FolderStructure) data(index *core.QModelIndex, role int) *core.QVariant {
row, isValid := index.Row(), index.IsValid()
if !isValid || row >= s.getCount() {
log.Warnln("Wrong index", isValid, row)
return core.NewQVariant()
}
var f = s.get(row)
switch role {
case FolderId:
return qtcommon.NewQVariantString(f.mailbox.Hash())
case FolderName, int(core.Qt__DisplayRole):
return qtcommon.NewQVariantString(f.mailbox.Name)
case FolderColor:
return qtcommon.NewQVariantString(f.mailbox.Color)
case FolderType:
return qtcommon.NewQVariantString(f.FolderType)
case FolderEntries:
return qtcommon.NewQVariantInt(f.FolderEntries)
case FolderFromDate:
return qtcommon.NewQVariantLong(f.FromDate)
case FolderToDate:
return qtcommon.NewQVariantLong(f.ToDate)
case IsFolderSelected:
return qtcommon.NewQVariantBool(f.IsFolderSelected)
case TargetFolderID:
return qtcommon.NewQVariantString(f.TargetFolderID)
case TargetLabelIDs:
return qtcommon.NewQVariantString(f.TargetLabelIDs)
default:
log.Warnln("Wrong role", role)
return core.NewQVariant()
}
}
// Get header data (table view, tree view)
func (s *FolderStructure) headerData(section int, orientation core.Qt__Orientation, role int) *core.QVariant {
if role != int(core.Qt__DisplayRole) {
return core.NewQVariant()
}
if orientation == core.Qt__Horizontal {
return qtcommon.NewQVariantString("Column")
}
return qtcommon.NewQVariantString("Row")
}
// Flags is editable
func (s *FolderStructure) flags(index *core.QModelIndex) core.Qt__ItemFlag {
if !index.IsValid() {
return core.Qt__ItemIsEnabled
}
// can do here also: core.NewQAbstractItemModelFromPointer(s.Pointer()).Flags(index) | core.Qt__ItemIsEditable
// or s.FlagsDefault(index) | core.Qt__ItemIsEditable
return core.Qt__ItemIsEnabled | core.Qt__ItemIsSelectable | core.Qt__ItemIsEditable
}
// Set data
func (s *FolderStructure) setData(index *core.QModelIndex, value *core.QVariant, role int) bool {
log.Debugf("SET DATA %d", role)
if !index.IsValid() {
return false
}
if index.Row() < GlobalOptionIndex || index.Row() > s.getCount() || index.Column() != 1 {
return false
}
item := s.get(index.Row())
t := true
switch role {
case FolderId, FolderType:
log.
WithField("structure", s).
WithField("row", index.Row()).
WithField("column", index.Column()).
WithField("role", role).
WithField("isEdit", role == int(core.Qt__EditRole)).
Warn("Set constant role forbiden")
case FolderName:
item.mailbox.Name = value.ToString()
case FolderColor:
item.mailbox.Color = value.ToString()
case FolderEntries:
item.FolderEntries = value.ToInt(&t)
case FolderFromDate:
item.FromDate = value.ToLongLong(&t)
case FolderToDate:
item.ToDate = value.ToLongLong(&t)
case IsFolderSelected:
item.IsFolderSelected = value.ToBool()
case TargetFolderID:
item.TargetFolderID = value.ToString()
case TargetLabelIDs:
item.TargetLabelIDs = value.ToString()
default:
log.Debugln("uknown role ", s, index.Row(), index.Column(), role, role == int(core.Qt__EditRole))
return false
}
s.changedEntityRole(index.Row(), index.Row(), role)
return true
}
// Dimension of model: number of rows is equivalent to number of items in list
func (s *FolderStructure) rowCount(parent *core.QModelIndex) int {
return s.getCount()
}
func (s *FolderStructure) getCount() int {
return len(s.entities)
}
// Returns names of available item properties
func (s *FolderStructure) roleNames() map[int]*core.QByteArray {
return s.Roles()
}
// Clear removes all items in model
func (s *FolderStructure) Clear() {
s.BeginResetModel()
if s.getCount() != 0 {
s.entities = []*FolderInfo{}
}
s.GlobalOptions = FolderInfo{
mailbox: transfer.Mailbox{
Name: "=",
},
FromDate: 0,
ToDate: 0,
TargetFolderID: "",
TargetLabelIDs: "",
}
s.EndResetModel()
}
// Method connected to addEntry slot
func (s *FolderStructure) addEntry(entry *FolderInfo) {
s.insertEntry(entry, s.getCount())
}
// NewUniqId which is not in map yet.
func (s *FolderStructure) newUniqId() (name string) {
name = s.GlobalOptions.mailbox.Name
mbox := transfer.Mailbox{Name: name}
for newVal := byte(name[0]); true; newVal++ {
mbox.Name = string([]byte{newVal})
if s.getRowById(mbox.Hash()) < GlobalOptionIndex {
return
}
}
return
}
// Method connected to addEntry slot
func (s *FolderStructure) insertEntry(entry *FolderInfo, i int) {
s.BeginInsertRows(core.NewQModelIndex(), i, i)
s.entities = append(s.entities[:i], append([]*FolderInfo{entry}, s.entities[i:]...)...)
s.EndInsertRows()
// update global if conflict
if entry.mailbox.Hash() == s.GlobalOptions.mailbox.Hash() {
globalName := s.newUniqId()
s.GlobalOptions.mailbox.Name = globalName
}
}
func (s *FolderStructure) GetInfo(row int) FolderInfo {
return *s.get(row)
}
func (s *FolderStructure) changedEntityRole(rowStart int, rowEnd int, roles ...int) {
if rowStart < GlobalOptionIndex || rowEnd < GlobalOptionIndex {
return
}
if rowStart < 0 || rowStart >= s.getCount() {
rowStart = 0
}
if rowEnd < 0 || rowEnd >= s.getCount() {
rowEnd = s.getCount()
}
if rowStart > rowEnd {
tmp := rowStart
rowStart = rowEnd
rowEnd = tmp
}
indexStart := s.Index(rowStart, 0, core.NewQModelIndex())
indexEnd := s.Index(rowEnd, 0, core.NewQModelIndex())
s.updateSelection(indexStart, indexEnd, roles)
s.DataChanged(indexStart, indexEnd, roles)
}
func (s *FolderStructure) setFolderSelection(id string, toSelect bool) {
log.Debugf("set folder selection %q %b", id, toSelect)
i := s.getRowById(id)
//
info := s.get(i)
before := info.IsFolderSelected
info.IsFolderSelected = toSelect
if err := s.saveRule(info); err != nil {
s.get(i).IsFolderSelected = before
log.WithError(err).WithField("id", id).WithField("toSelect", toSelect).Error("Cannot set selection")
return
}
//
s.changedEntityRole(i, i, IsFolderSelected)
}
func (s *FolderStructure) setTargetFolderID(id, target string) {
log.Debugf("set targetFolderID %q %q", id, target)
i := s.getRowById(id)
//
info := s.get(i)
//s.get(i).TargetFolderID = target
before := info.TargetFolderID
info.TargetFolderID = target
if err := s.saveRule(info); err != nil {
info.TargetFolderID = before
log.WithError(err).WithField("id", id).WithField("target", target).Error("Cannot set target")
return
}
//
s.changedEntityRole(i, i, TargetFolderID)
if target == "" { // do not import
before := info.TargetLabelIDs
info.TargetLabelIDs = ""
if err := s.saveRule(info); err != nil {
info.TargetLabelIDs = before
log.WithError(err).WithField("id", id).WithField("target", target).Error("Cannot set target")
return
}
s.changedEntityRole(i, i, TargetLabelIDs)
}
}
func (s *FolderStructure) addTargetLabelID(id, label string) {
log.Debugf("add target label id %q %q", id, label)
if label == "" {
return
}
i := s.getRowById(id)
info := s.get(i)
before := info.TargetLabelIDs
info.AddTargetLabel(label)
if err := s.saveRule(info); err != nil {
info.TargetLabelIDs = before
log.WithError(err).WithField("id", id).WithField("label", label).Error("Cannot add label")
return
}
s.changedEntityRole(i, i, TargetLabelIDs)
}
func (s *FolderStructure) removeTargetLabelID(id, label string) {
log.Debugf("remove label id %q %q", id, label)
if label == "" {
return
}
i := s.getRowById(id)
info := s.get(i)
before := info.TargetLabelIDs
info.RemoveTargetLabel(label)
if err := s.saveRule(info); err != nil {
info.TargetLabelIDs = before
log.WithError(err).WithField("id", id).WithField("label", label).Error("Cannot remove label")
return
}
s.changedEntityRole(i, i, TargetLabelIDs)
}
func (s *FolderStructure) setFromToDate(id string, from, to int64) {
log.Debugf("set from to date %q %d %d", id, from, to)
i := s.getRowById(id)
info := s.get(i)
beforeFrom := info.FromDate
beforeTo := info.ToDate
info.FromDate = from
info.ToDate = to
if err := s.saveRule(info); err != nil {
info.FromDate = beforeFrom
info.ToDate = beforeTo
log.WithError(err).WithField("id", id).WithField("from", from).WithField("to", to).Error("Cannot set date")
return
}
s.changedEntityRole(i, i, FolderFromDate, FolderToDate)
}
func (s *FolderStructure) selectType(folderType string, toSelect bool) {
log.Debugf("set type %q %b", folderType, toSelect)
iFirst, iLast := -1, -1
for i, entity := range s.entities {
if entity.IsType(folderType) {
if iFirst == -1 {
iFirst = i
}
before := entity.IsFolderSelected
entity.IsFolderSelected = toSelect
if err := s.saveRule(entity); err != nil {
entity.IsFolderSelected = before
log.WithError(err).WithField("i", i).WithField("type", folderType).WithField("toSelect", toSelect).Error("Cannot select type")
}
iLast = i
}
}
if iFirst != -1 {
s.changedEntityRole(iFirst, iLast, IsFolderSelected)
}
}
func (s *FolderStructure) updateSelection(topLeft *core.QModelIndex, bottomRight *core.QModelIndex, roles []int) {
for _, role := range roles {
switch role {
case IsFolderSelected:
s.SetSelectedFolders(true)
s.SetSelectedLabels(true)
s.SetAtLeastOneSelected(false)
for _, entity := range s.entities {
if entity.IsFolderSelected {
s.SetAtLeastOneSelected(true)
} else {
if entity.IsType(FolderTypeFolder) {
s.SetSelectedFolders(false)
}
if entity.IsType(FolderTypeLabel) {
s.SetSelectedLabels(false)
}
}
if !s.IsSelectedFolders() && !s.IsSelectedLabels() && s.IsAtLeastOneSelected() {
break
}
}
default:
}
}
}
func (s *FolderStructure) hasFolderWithName(name string) bool {
for _, entity := range s.entities {
if entity.mailbox.Name == name {
return true
}
}
return false
}
func (s *FolderStructure) getRowById(id string) (row int) {
for row = GlobalOptionIndex; row < s.getCount(); row++ {
if id == s.get(row).mailbox.Hash() {
return
}
}
row = GlobalOptionIndex - 1
return
}
func (s *FolderStructure) hasTarget() bool {
for row := 0; row < s.getCount(); row++ {
if s.get(row).TargetFolderID != "" {
return true
}
}
return false
}
// Getter for account info pointer
// index out of array length returns empty folder info to avoid segfault
// index == GlobalOptionIndex is set to access global options
func (s *FolderStructure) get(index int) *FolderInfo {
if index < GlobalOptionIndex || index >= s.getCount() {
return &FolderInfo{}
}
if index == GlobalOptionIndex {
return &s.GlobalOptions
}
return s.entities[index]
}