From 235e3797783f4fe3f0ee9c00b9ba294b0d60be3b Mon Sep 17 00:00:00 2001 From: Robert Sesek Date: Sun, 16 Nov 2025 16:01:08 -0500 Subject: [PATCH] Start scaffolding the Source and Destination accessors --- cmd/mailbox-shuffler/config.go | 12 +-- cmd/mailbox-shuffler/dest.go | 44 +++++++++++ cmd/mailbox-shuffler/monitor.go | 28 +++++++ cmd/mailbox-shuffler/source.go | 130 ++++++++++++++++++++++++++++++++ 4 files changed, 209 insertions(+), 5 deletions(-) create mode 100644 cmd/mailbox-shuffler/dest.go create mode 100644 cmd/mailbox-shuffler/monitor.go create mode 100644 cmd/mailbox-shuffler/source.go diff --git a/cmd/mailbox-shuffler/config.go b/cmd/mailbox-shuffler/config.go index 3972b54..4620d37 100644 --- a/cmd/mailbox-shuffler/config.go +++ b/cmd/mailbox-shuffler/config.go @@ -28,12 +28,14 @@ type ServerConfig struct { Password string } +type MonitorConfig struct { + Source ServerConfig + Destination ServerConfig + PollInterval time.Duration +} + type Config struct { - Monitor []struct { - Source ServerConfig - Destination ServerConfig - PollInterval time.Duration - } + Monitor []MonitorConfig OAuthServer struct { RedirectURL string diff --git a/cmd/mailbox-shuffler/dest.go b/cmd/mailbox-shuffler/dest.go new file mode 100644 index 0000000..6398481 --- /dev/null +++ b/cmd/mailbox-shuffler/dest.go @@ -0,0 +1,44 @@ +// mailpopbox +// Copyright 2025 Blue Static +// This program is free software licensed under the GNU General Public License, +// version 3.0. The full text of the license can be found in LICENSE.txt. +// SPDX-License-Identifier: GPL-3.0-only + +package main + +import "go.uber.org/zap" + +type Destination interface { + // AddMessage stores the raw RFC 2822 message body in the destination mail + // server. + AddMessage([]byte) error + // Close releases any connection resources on the Destination. + Close() error +} + +func NewDestination(config ServerConfig, auth *OAuthServer, log *zap.Logger) Destination { + switch config.Type { + case ServerTypeGmail: + return &gmailDestination{ + c: config, + auth: auth, + log: log, + } + default: + panic("Unsupported destination server type") + } +} + +type gmailDestination struct { + c ServerConfig + auth *OAuthServer + log *zap.Logger +} + +func (d *gmailDestination) AddMessage(msg []byte) error { + return nil +} + +func (d *gmailDestination) Close() error { + return nil +} diff --git a/cmd/mailbox-shuffler/monitor.go b/cmd/mailbox-shuffler/monitor.go new file mode 100644 index 0000000..7e18f38 --- /dev/null +++ b/cmd/mailbox-shuffler/monitor.go @@ -0,0 +1,28 @@ +// mailpopbox +// Copyright 2025 Blue Static +// This program is free software licensed under the GNU General Public License, +// version 3.0. The full text of the license can be found in LICENSE.txt. +// SPDX-License-Identifier: GPL-3.0-only + +package main + +import ( + "context" + + "go.uber.org/zap" +) + +type Monitor struct { + c MonitorConfig + log *zap.Logger +} + +func NewMontior(config MonitorConfig, log *zap.Logger) *Monitor { + return &Monitor{ + c: config, + log: log, + } +} + +func (m *Monitor) Start(ctx context.Context) { +} diff --git a/cmd/mailbox-shuffler/source.go b/cmd/mailbox-shuffler/source.go new file mode 100644 index 0000000..bdc8644 --- /dev/null +++ b/cmd/mailbox-shuffler/source.go @@ -0,0 +1,130 @@ +// mailpopbox +// Copyright 2025 Blue Static +// This program is free software licensed under the GNU General Public License, +// version 3.0. The full text of the license can be found in LICENSE.txt. +// SPDX-License-Identifier: GPL-3.0-only + +package main + +import ( + "crypto/tls" + "fmt" + "io" + "net" + + "src.bluestatic.org/mailpopbox/pkg/pop3" + + "go.uber.org/zap" +) + +type Source interface { + // GetMessages returns the list of available messages on the server. The + // returned Message objects are only valid until `Close` is called. + GetMessages() ([]Message, error) + // Reset attempts to rollback the transaction on the server. + Reset() error + // Close releases any connection resources on the Source. + Close() error +} + +type Message interface { + ID() string + Content() (io.ReadCloser, error) + Delete() error +} + +// NewSource creates an interface for accessing a message source. The returned +// object is *not* goroutine safe. +func NewSource(config ServerConfig, auth *OAuthServer, log *zap.Logger) Source { + switch config.Type { + case ServerTypePOP3: + return &pop3Source{ + c: config, + log: log, + } + default: + panic("Unsupported source server type") + } +} + +type pop3Source struct { + c ServerConfig + log *zap.Logger + + po pop3.PostOffice + mbox pop3.Mailbox +} + +type pop3Message struct { + s *pop3Source + msg pop3.Message +} + +func (m *pop3Message) ID() string { return fmt.Sprintf("%d", m.msg.ID()) } +func (m *pop3Message) Content() (io.ReadCloser, error) { return m.s.mbox.Retrieve(m.msg) } +func (m *pop3Message) Delete() error { return m.s.mbox.Delete(m.msg) } + +var errNotConnected = fmt.Errorf("Source is not connected") + +func (s *pop3Source) GetMessages() ([]Message, error) { + if err := s.connect(); err != nil { + return nil, err + } + pmsgs, err := s.mbox.ListMessages() + if err != nil { + return nil, err + } + msgs := make([]Message, len(pmsgs)) + for i, pmsg := range pmsgs { + msgs[i] = &pop3Message{s: s, msg: pmsg} + } + return msgs, nil +} + +func (s *pop3Source) connect() error { + if s.po != nil && s.mbox != nil { + return nil + } + + var nc net.Conn + var err error + if s.c.UseTLS { + nc, err = tls.Dial("tcp", s.c.ServerAddr, nil) + } else { + nc, err = net.Dial("tcp", s.c.ServerAddr) + } + if err != nil { + return err + } + + po, err := pop3.Connect(nc, s.log) + if err != nil { + return err + } + s.po = po + mbox, err := s.po.OpenMailbox(s.c.Email, s.c.Password) + if err != nil { + s.po = nil + return err + } + s.mbox = mbox + return nil +} + +func (s *pop3Source) Reset() error { + if s.po == nil || s.mbox == nil { + return errNotConnected + } + s.mbox.Reset() + return nil +} + +func (s *pop3Source) Close() error { + if s.po == nil || s.mbox == nil { + return errNotConnected + } + err := s.mbox.Close() + s.po = nil + s.mbox = nil + return err +} -- 2.43.5