diff --git a/go.mod b/go.mod index ccca6a9..bf8e12d 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.24.0 require ( github.com/emersion/go-imap v1.2.1 github.com/emersion/go-imap-idle v0.0.0-20210907174914-db2568431445 + github.com/emersion/go-imap-move v0.0.0-20210907172020-fe4558f9c872 github.com/emersion/go-message v0.17.0 github.com/emersion/go-sasl v0.0.0-20220912192320-0145f2c60ead github.com/emersion/go-smtp v0.15.0 diff --git a/go.sum b/go.sum index 6ecc7db..17dd8c9 100644 --- a/go.sum +++ b/go.sum @@ -17,6 +17,8 @@ github.com/emersion/go-imap v1.2.1 h1:+s9ZjMEjOB8NzZMVTM3cCenz2JrQIGGo5j1df19WjT github.com/emersion/go-imap v1.2.1/go.mod h1:Qlx1FSx2FTxjnjWpIlVNEuX+ylerZQNFE5NsmKFSejY= github.com/emersion/go-imap-idle v0.0.0-20210907174914-db2568431445 h1:dAGbaaU4LLupO7dnYZaELOoI3RoVDNi5DCGejLe8a7c= github.com/emersion/go-imap-idle v0.0.0-20210907174914-db2568431445/go.mod h1:N/6S3dRTVt8xT867m+476C16+v/Fq4WZYvh2Chg0nmg= +github.com/emersion/go-imap-move v0.0.0-20210907172020-fe4558f9c872 h1:HGBfonz0q/zq7y3ew+4oy4emHSvk6bkmV0mdDG3E77M= +github.com/emersion/go-imap-move v0.0.0-20210907172020-fe4558f9c872/go.mod h1:QuMaZcKFDVI0yCrnAbPLfbwllz1wtOrZH8/vZ5yzp4w= github.com/emersion/go-message v0.11.1/go.mod h1:C4jnca5HOTo4bGN9YdqNQM9sITuT3Y0K6bSUw9RklvY= github.com/emersion/go-message v0.15.0/go.mod h1:wQUEfE+38+7EW8p8aZ96ptg6bAb1iwdgej19uXASlE4= github.com/emersion/go-message v0.17.0 h1:NIdSKHiVUx4qKqdd0HyJFD41cW8iFguM2XJnRZWQH04= diff --git a/internal/imapserver/imap.go b/internal/imapserver/imap.go index 26cde90..02e0628 100644 --- a/internal/imapserver/imap.go +++ b/internal/imapserver/imap.go @@ -12,6 +12,7 @@ import ( "log" idle "github.com/emersion/go-imap-idle" + move "github.com/emersion/go-imap-move" "github.com/emersion/go-imap/server" "github.com/emersion/go-sasl" ) @@ -19,6 +20,7 @@ import ( type IMAPServer struct { server *server.Server backend *Backend + notify *IMAPNotify } func NewIMAPServer(backend *Backend, addr string, insecure bool) (*IMAPServer, *IMAPNotify, error) { @@ -26,12 +28,13 @@ func NewIMAPServer(backend *Backend, addr string, insecure bool) (*IMAPServer, * server: server.New(backend), backend: backend, } - notify := NewIMAPNotify(s.server, backend.Log) + s.notify = NewIMAPNotify(s.server, backend.Log) s.server.Addr = addr s.server.AllowInsecureAuth = insecure //s.server.Debug = os.Stdout s.server.Enable(idle.NewExtension()) - //s.server.Enable(notify) + s.server.Enable(move.NewExtension()) + // s.server.Enable(s.notify) s.server.EnableAuth(sasl.Login, func(conn server.Conn) sasl.Server { return sasl.NewLoginServer(func(username, password string) error { _, err := s.backend.Login(nil, username, password) @@ -43,5 +46,5 @@ func NewIMAPServer(backend *Backend, addr string, insecure bool) (*IMAPServer, * log.Fatal(err) } }() - return s, notify, nil + return s, s.notify, nil } diff --git a/internal/imapserver/mailbox.go b/internal/imapserver/mailbox.go index df4e93b..ada5540 100644 --- a/internal/imapserver/mailbox.go +++ b/internal/imapserver/mailbox.go @@ -13,7 +13,6 @@ import ( "bytes" "fmt" "io" - "io/ioutil" "time" "github.com/emersion/go-imap" @@ -214,7 +213,7 @@ func (mbox *Mailbox) SearchMessages(uid bool, criteria *imap.SearchCriteria) ([] } func (mbox *Mailbox) CreateMessage(flags []string, date time.Time, body imap.Literal) error { - b, err := ioutil.ReadAll(body) + b, err := io.ReadAll(body) if err != nil { return fmt.Errorf("b.ReadFrom: %w", err) } @@ -312,3 +311,21 @@ func (mbox *Mailbox) CopyMessages(uid bool, seqSet *imap.SeqSet, destName string func (mbox *Mailbox) Expunge() error { return mbox.backend.Storage.MailExpunge(mbox.name) } + +func (mbox *Mailbox) MoveMessages(uid bool, seqset *imap.SeqSet, dest string) error { + if dest == "Outbox" { + return fmt.Errorf("can't copy into Outbox as it is a protected folder") + } + + ids, err := mbox.getIDsFromSeqSet(uid, seqset) + if err != nil { + return fmt.Errorf("mbox.getIDsFromSeqSet: %w", err) + } + + for _, id := range ids { + if err := mbox.backend.Storage.MailMove(mbox.name, int(id), dest); err != nil { + return err + } + } + return nil +} diff --git a/internal/storage/sqlite3/table_mails.go b/internal/storage/sqlite3/table_mails.go index c5279c8..5e76b87 100644 --- a/internal/storage/sqlite3/table_mails.go +++ b/internal/storage/sqlite3/table_mails.go @@ -30,6 +30,7 @@ type TableMails struct { updateMailFlags *sql.Stmt deleteMail *sql.Stmt expungeMail *sql.Stmt + moveMail *sql.Stmt } const mailsSchema = ` @@ -94,7 +95,7 @@ const selectIDForSeqStmt = ` const selectMailNextID = ` SELECT IFNULL(MAX(id)+1,1) AS id FROM mails - WHERE mailbox = $1 + WHERE mailbox = $1 ` const updateMailFlagsStmt = ` @@ -109,6 +110,10 @@ const expungeMailStmt = ` DELETE FROM mails WHERE mailbox = $1 AND deleted = 1 ` +const moveMailStmt = ` + UPDATE mails SET mailbox = $1 WHERE mailbox = $2 AND id = $3 +` + func NewTableMails(db *sql.DB, writer *Writer) (*TableMails, error) { t := &TableMails{ db: db, @@ -162,6 +167,10 @@ func NewTableMails(db *sql.DB, writer *Writer) (*TableMails, error) { if err != nil { return nil, fmt.Errorf("db.Prepare(selectMailUnseenStmt): %w", err) } + t.moveMail, err = db.Prepare(moveMailStmt) + if err != nil { + return nil, fmt.Errorf("db.Prepare(moveMailStmt): %w", err) + } return t, nil } @@ -246,3 +255,10 @@ func (t *TableMails) MailCount(mailbox string) (int, error) { err := t.countMails.QueryRow(mailbox).Scan(&count) return count, err } + +func (t *TableMails) MailMove(mailbox string, id int, destination string) error { + return t.writer.Do(t.db, nil, func(txn *sql.Tx) error { + _, err := t.moveMail.Exec(destination, mailbox, id) + return err + }) +} diff --git a/internal/storage/storage.go b/internal/storage/storage.go index abd1e3a..1441265 100644 --- a/internal/storage/storage.go +++ b/internal/storage/storage.go @@ -33,6 +33,7 @@ type Storage interface { MailDelete(mailbox string, id int) error MailExpunge(mailbox string) error MailCount(mailbox string) (int, error) + MailMove(mailbox string, id int, destination string) error QueueListDestinations() ([]string, error) QueueMailIDsForDestination(destination string) ([]types.QueuedMail, error)