// 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 . // Package transfer provides tools to export messages from one provider and // import them to another provider. Provider can be EML, MBOX, IMAP or PMAPI. package transfer import ( "crypto/sha256" "fmt" "github.com/sirupsen/logrus" ) var log = logrus.WithField("pkg", "transfer") //nolint[gochecknoglobals] // Transfer is facade on top of import rules, progress manager and source // and target providers. This is the main object which should be used. type Transfer struct { panicHandler PanicHandler metrics MetricsManager id string logDir string rules transferRules source SourceProvider target TargetProvider rulesCache []*Rule sourceMboxCache []Mailbox targetMboxCache []Mailbox } // New creates Transfer for specific source and target. Usage: // // source := transfer.NewEMLProvider(...) // target := transfer.NewPMAPIProvider(...) // transfer.New(source, target, ...) func New(panicHandler PanicHandler, metrics MetricsManager, logDir, rulesDir string, source SourceProvider, target TargetProvider) (*Transfer, error) { transferID := fmt.Sprintf("%x", sha256.Sum256([]byte(source.ID()+"-"+target.ID()))) rules := loadRules(rulesDir, transferID) transfer := &Transfer{ panicHandler: panicHandler, metrics: metrics, id: transferID, logDir: logDir, rules: rules, source: source, target: target, } if err := transfer.setDefaultRules(); err != nil { return nil, err } metrics.Load(len(transfer.sourceMboxCache)) return transfer, nil } // SetDefaultRules sets missing rules for source mailboxes with matching // target mailboxes. In case no matching mailbox is found, `defaultCallback` // with a source mailbox as a parameter is used. func (t *Transfer) setDefaultRules() error { sourceMailboxes, err := t.SourceMailboxes() if err != nil { return err } targetMailboxes, err := t.TargetMailboxes() if err != nil { return err } defaultCallback := func(sourceMailbox Mailbox) []Mailbox { return t.target.DefaultMailboxes(sourceMailbox) } t.rules.setDefaultRules(sourceMailboxes, targetMailboxes, defaultCallback) return nil } // SetSkipEncryptedMessages sets whether message which cannot be decrypted // should be exported or skipped. func (t *Transfer) SetSkipEncryptedMessages(skip bool) { t.rules.setSkipEncryptedMessages(skip) } // SetGlobalMailbox sets mailbox that is applied to every message in // the import phase. func (t *Transfer) SetGlobalMailbox(mailbox *Mailbox) { t.rules.setGlobalMailbox(mailbox) } // SetGlobalTimeLimit sets time limit that is applied to rules without any // specified time limit. func (t *Transfer) SetGlobalTimeLimit(fromTime, toTime int64) { t.rules.setGlobalTimeLimit(fromTime, toTime) } // SetRule sets sourceMailbox for transfer. func (t *Transfer) SetRule(sourceMailbox Mailbox, targetMailboxes []Mailbox, fromTime, toTime int64) error { t.rulesCache = nil return t.rules.setRule(sourceMailbox, targetMailboxes, fromTime, toTime) } // UnsetRule unsets sourceMailbox from transfer. func (t *Transfer) UnsetRule(sourceMailbox Mailbox) { t.rulesCache = nil t.rules.unsetRule(sourceMailbox) } // ResetRules unsets all rules. func (t *Transfer) ResetRules() { t.rulesCache = nil t.rules.reset() } // GetRule returns rule for given mailbox. func (t *Transfer) GetRule(sourceMailbox Mailbox) *Rule { return t.rules.getRule(sourceMailbox) } // GetRules returns all set transfer rules. func (t *Transfer) GetRules() []*Rule { if t.rulesCache == nil { t.rulesCache = t.rules.getSortedRules() } return t.rulesCache } // SourceMailboxes returns mailboxes available at source side. func (t *Transfer) SourceMailboxes() (m []Mailbox, err error) { if t.sourceMboxCache == nil { t.sourceMboxCache, err = t.source.Mailboxes(false, true) } return t.sourceMboxCache, err } // TargetMailboxes returns mailboxes available at target side. func (t *Transfer) TargetMailboxes() (m []Mailbox, err error) { if t.targetMboxCache == nil { t.targetMboxCache, err = t.target.Mailboxes(true, false) } return t.targetMboxCache, err } // CreateTargetMailbox creates mailbox in target provider. func (t *Transfer) CreateTargetMailbox(mailbox Mailbox) (Mailbox, error) { t.targetMboxCache = nil return t.target.CreateMailbox(mailbox) } // ChangeTarget changes the target. It is safe to change target for export, // must not be changed for import. Do not set after you started transfer. func (t *Transfer) ChangeTarget(target TargetProvider) { t.targetMboxCache = nil t.target = target } // Start starts the transfer from source to target. func (t *Transfer) Start() *Progress { log.Debug("Transfer started") t.rules.save() t.rules.propagateGlobalTime() t.metrics.Start() log := log.WithField("id", t.id) reportFile := newFileReport(t.logDir, t.id) progress := newProgress(log, reportFile) // Small queue to prevent having idle source while target is blocked. // E.g., when upload to PM is in progress, we can in meantime download // the next batch from remote IMAP server. ch := make(chan Message, 10) go func() { defer t.panicHandler.HandlePanic() t.source.TransferTo(t.rules, &progress, ch) close(ch) }() go func() { defer t.panicHandler.HandlePanic() t.target.TransferFrom(t.rules, &progress, ch) progress.finish() if progress.isStopped { if progress.fatalError != nil { t.metrics.Fail() } else { t.metrics.Cancel() } } else { t.metrics.Complete() } }() return &progress }