aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--internal/imap/client.go202
-rw-r--r--internal/imap/cmds.go27
-rw-r--r--internal/imap/commando.go17
-rw-r--r--internal/imap/connection.go137
-rw-r--r--internal/imap/folder.go33
-rw-r--r--internal/imap/imap.go57
-rw-r--r--internal/imap/mailboxes.go34
-rw-r--r--internal/imap/url.go76
8 files changed, 316 insertions, 267 deletions
diff --git a/internal/imap/client.go b/internal/imap/client.go
index 5e2546b..404c03e 100644
--- a/internal/imap/client.go
+++ b/internal/imap/client.go
@@ -1,12 +1,6 @@
package imap
import (
- "fmt"
- "strings"
- "sync"
- "time"
-
- "github.com/emersion/go-imap"
imapClient "github.com/emersion/go-imap/client"
"github.com/Necoro/feed2imap-go/internal/log"
@@ -20,75 +14,21 @@ type connConf struct {
toplevel Folder
}
-type connection struct {
- *connConf
- mailboxes *mailboxes
- c *imapClient.Client
-}
-
-type mailboxes struct {
- mb map[string]*imap.MailboxInfo
- mu sync.RWMutex
-}
-
type Client struct {
connConf
- mailboxes mailboxes
+ mailboxes *mailboxes
commander *commander
connections [numberConns]*connection
nextFreeIndex int
}
-type Folder struct {
- str string
- delimiter string
-}
-
-func (f Folder) String() string {
- return f.str
-}
-
-func (f Folder) Append(other Folder) Folder {
- if f.delimiter != other.delimiter {
- panic("Delimiters do not match")
- }
- return Folder{
- str: f.str + f.delimiter + other.str,
- delimiter: f.delimiter,
- }
-}
-
-func (mbs *mailboxes) contains(elem Folder) bool {
- mbs.mu.RLock()
- defer mbs.mu.RUnlock()
-
- _, ok := mbs.mb[elem.str]
- return ok
-}
-
-func (mbs *mailboxes) add(elem *imap.MailboxInfo) {
- mbs.mu.Lock()
- defer mbs.mu.Unlock()
-
- mbs.mb[elem.Name] = elem
-}
-
-func (conn *connection) Disconnect() bool {
- if conn != nil {
- connected := (conn.c.State() & imap.ConnectedState) != 0
- _ = conn.c.Logout()
- return connected
- }
- return false
-}
-
func (client *Client) Disconnect() {
if client != nil {
client.stopCommander()
connected := false
for _, conn := range client.connections {
- connected = conn.Disconnect() || connected
+ connected = conn.disconnect() || connected
}
if connected {
@@ -97,116 +37,6 @@ func (client *Client) Disconnect() {
}
}
-func (client *Client) folderName(path []string) Folder {
- return Folder{
- strings.Join(path, client.delimiter),
- client.delimiter,
- }
-}
-
-func (client *Client) NewFolder(path []string) Folder {
- return client.toplevel.Append(client.folderName(path))
-}
-
-func (conn *connection) createFolder(folder string) error {
- err := conn.c.Create(folder)
- if err != nil {
- return fmt.Errorf("creating folder '%s': %w", folder, err)
- }
-
- err = conn.c.Subscribe(folder)
- if err != nil {
- return fmt.Errorf("subscribing to folder '%s': %w", folder, err)
- }
-
- log.Printf("Created folder '%s'", folder)
-
- return nil
-}
-
-func (conn *connection) list(folder string) (*imap.MailboxInfo, int, error) {
- mailboxes := make(chan *imap.MailboxInfo, 10)
- done := make(chan error, 1)
- go func() {
- done <- conn.c.List("", folder, mailboxes)
- }()
-
- found := 0
- var mbox *imap.MailboxInfo
- for m := range mailboxes {
- if found == 0 {
- mbox = m
- }
- found++
- }
-
- if err := <-done; err != nil {
- return nil, 0, fmt.Errorf("while listing '%s': %w", folder, err)
- }
-
- return mbox, found, nil
-}
-
-func (conn *connection) fetchDelimiter() (string, error) {
- mbox, _, err := conn.list("")
- if err != nil {
- return "", err
- }
-
- return mbox.Delimiter, nil
-}
-
-func (conn *connection) ensureFolder(folder Folder) error {
- if conn.mailboxes.contains(folder) {
- return nil
- }
-
- log.Printf("Checking for folder '%s'", folder)
-
- mbox, found, err := conn.list(folder.str)
- if err != nil {
- return err
- }
-
- if mbox != nil && mbox.Delimiter != folder.delimiter {
- panic("Delimiters do not match")
- }
-
- switch found {
- case 0:
- return conn.createFolder(folder.str)
- case 1:
- conn.mailboxes.add(mbox)
- return nil
- default:
- return fmt.Errorf("Found multiple folders matching '%s'.", folder)
- }
-}
-
-func (client *Client) EnsureFolder(folder Folder) error {
- return client.commander.execute(ensureCommando{folder})
-}
-
-func (conn *connection) putMessages(folder Folder, messages []string) error {
- if len(messages) == 0 {
- return nil
- }
-
- now := time.Now()
- for _, msg := range messages {
- reader := strings.NewReader(msg)
- if err := conn.c.Append(folder.str, nil, now, reader); err != nil {
- return fmt.Errorf("uploading message to %s: %w", folder, err)
- }
- }
-
- return nil
-}
-
-func (client *Client) PutMessages(folder Folder, messages []string) error {
- return client.commander.execute(addCommando{folder, messages})
-}
-
func (client *Client) createConnection(c *imapClient.Client) *connection{
if client.nextFreeIndex >= len(client.connections) {
panic("Too many connections")
@@ -214,7 +44,7 @@ func (client *Client) createConnection(c *imapClient.Client) *connection{
conn := &connection{
connConf: &client.connConf,
- mailboxes: &client.mailboxes,
+ mailboxes: client.mailboxes,
c: c,
}
@@ -224,30 +54,6 @@ func (client *Client) createConnection(c *imapClient.Client) *connection{
return conn
}
-func (conn *connection) startTls() error {
- hasStartTls, err := conn.c.SupportStartTLS()
- if err != nil {
- return fmt.Errorf("checking for starttls for %s: %w", conn.host, err)
- }
-
- if hasStartTls {
- if err = conn.c.StartTLS(nil); err != nil {
- return fmt.Errorf("enabling starttls for %s: %w", conn.host, err)
- }
-
- log.Print("Connected to ", conn.host, " (STARTTLS)")
- } else {
- log.Print("Connected to ", conn.host, " (Plain)")
- }
-
- return nil
-}
-
func NewClient() *Client {
- return &Client{
- mailboxes: mailboxes{
- mb: map[string]*imap.MailboxInfo{},
- mu: sync.RWMutex{},
- },
- }
+ return &Client{mailboxes: NewMailboxes()}
} \ No newline at end of file
diff --git a/internal/imap/cmds.go b/internal/imap/cmds.go
new file mode 100644
index 0000000..499d6c0
--- /dev/null
+++ b/internal/imap/cmds.go
@@ -0,0 +1,27 @@
+package imap
+
+type ensureCommando struct {
+ folder Folder
+}
+
+func (cmd ensureCommando) execute(conn *connection) error {
+ return conn.ensureFolder(cmd.folder)
+}
+
+func (client *Client) EnsureFolder(folder Folder) error {
+ return client.commander.execute(ensureCommando{folder})
+}
+
+
+type addCommando struct {
+ folder Folder
+ messages []string
+}
+
+func (cmd addCommando) execute(conn *connection) error {
+ return conn.putMessages(cmd.folder, cmd.messages)
+}
+
+func (client *Client) PutMessages(folder Folder, messages []string) error {
+ return client.commander.execute(addCommando{folder, messages})
+}
diff --git a/internal/imap/commando.go b/internal/imap/commando.go
index 171a507..1cf688e 100644
--- a/internal/imap/commando.go
+++ b/internal/imap/commando.go
@@ -17,23 +17,6 @@ type execution struct {
done chan<- error
}
-type addCommando struct {
- folder Folder
- messages []string
-}
-
-func (cmd addCommando) execute(conn *connection) error {
- return conn.putMessages(cmd.folder, cmd.messages)
-}
-
-type ensureCommando struct {
- folder Folder
-}
-
-func (cmd ensureCommando) execute(conn *connection) error {
- return conn.ensureFolder(cmd.folder)
-}
-
func (commander *commander) execute(command command) error {
done := make(chan error)
commander.pipe <- execution{command, done}
diff --git a/internal/imap/connection.go b/internal/imap/connection.go
new file mode 100644
index 0000000..e0360bd
--- /dev/null
+++ b/internal/imap/connection.go
@@ -0,0 +1,137 @@
+package imap
+
+import (
+ "fmt"
+ "strings"
+ "time"
+
+ "github.com/emersion/go-imap"
+ imapClient "github.com/emersion/go-imap/client"
+
+ "github.com/Necoro/feed2imap-go/internal/log"
+)
+
+type connection struct {
+ *connConf
+ mailboxes *mailboxes
+ c *imapClient.Client
+}
+
+func (conn *connection) startTls() error {
+ hasStartTls, err := conn.c.SupportStartTLS()
+ if err != nil {
+ return fmt.Errorf("checking for starttls for %s: %w", conn.host, err)
+ }
+
+ if hasStartTls {
+ if err = conn.c.StartTLS(nil); err != nil {
+ return fmt.Errorf("enabling starttls for %s: %w", conn.host, err)
+ }
+
+ log.Print("Connected to ", conn.host, " (STARTTLS)")
+ } else {
+ log.Print("Connected to ", conn.host, " (Plain)")
+ }
+
+ return nil
+}
+
+func (conn *connection) disconnect() bool {
+ if conn != nil {
+ connected := (conn.c.State() & imap.ConnectedState) != 0
+ _ = conn.c.Logout()
+ return connected
+ }
+ return false
+}
+
+func (conn *connection) createFolder(folder string) error {
+ err := conn.c.Create(folder)
+ if err != nil {
+ return fmt.Errorf("creating folder '%s': %w", folder, err)
+ }
+
+ err = conn.c.Subscribe(folder)
+ if err != nil {
+ return fmt.Errorf("subscribing to folder '%s': %w", folder, err)
+ }
+
+ log.Printf("Created folder '%s'", folder)
+
+ return nil
+}
+
+func (conn *connection) list(folder string) (*imap.MailboxInfo, int, error) {
+ mailboxes := make(chan *imap.MailboxInfo, 10)
+ done := make(chan error, 1)
+ go func() {
+ done <- conn.c.List("", folder, mailboxes)
+ }()
+
+ found := 0
+ var mbox *imap.MailboxInfo
+ for m := range mailboxes {
+ if found == 0 {
+ mbox = m
+ }
+ found++
+ }
+
+ if err := <-done; err != nil {
+ return nil, 0, fmt.Errorf("while listing '%s': %w", folder, err)
+ }
+
+ return mbox, found, nil
+}
+
+func (conn *connection) fetchDelimiter() (string, error) {
+ mbox, _, err := conn.list("")
+ if err != nil {
+ return "", err
+ }
+
+ return mbox.Delimiter, nil
+}
+
+func (conn *connection) ensureFolder(folder Folder) error {
+ if conn.mailboxes.contains(folder) {
+ return nil
+ }
+
+ log.Printf("Checking for folder '%s'", folder)
+
+ mbox, found, err := conn.list(folder.str)
+ if err != nil {
+ return err
+ }
+
+ if mbox != nil && mbox.Delimiter != folder.delimiter {
+ panic("Delimiters do not match")
+ }
+
+ switch found {
+ case 0:
+ return conn.createFolder(folder.str)
+ case 1:
+ conn.mailboxes.add(mbox)
+ return nil
+ default:
+ return fmt.Errorf("Found multiple folders matching '%s'.", folder)
+ }
+}
+
+func (conn *connection) putMessages(folder Folder, messages []string) error {
+ if len(messages) == 0 {
+ return nil
+ }
+
+ now := time.Now()
+ for _, msg := range messages {
+ reader := strings.NewReader(msg)
+ if err := conn.c.Append(folder.str, nil, now, reader); err != nil {
+ return fmt.Errorf("uploading message to %s: %w", folder, err)
+ }
+ }
+
+ return nil
+}
diff --git a/internal/imap/folder.go b/internal/imap/folder.go
new file mode 100644
index 0000000..1f4e0bf
--- /dev/null
+++ b/internal/imap/folder.go
@@ -0,0 +1,33 @@
+package imap
+
+import "strings"
+
+type Folder struct {
+ str string
+ delimiter string
+}
+
+func (f Folder) String() string {
+ return f.str
+}
+
+func (f Folder) Append(other Folder) Folder {
+ if f.delimiter != other.delimiter {
+ panic("Delimiters do not match")
+ }
+ return Folder{
+ str: f.str + f.delimiter + other.str,
+ delimiter: f.delimiter,
+ }
+}
+
+func (client *Client) folderName(path []string) Folder {
+ return Folder{
+ strings.Join(path, client.delimiter),
+ client.delimiter,
+ }
+}
+
+func (client *Client) NewFolder(path []string) Folder {
+ return client.toplevel.Append(client.folderName(path))
+}
diff --git a/internal/imap/imap.go b/internal/imap/imap.go
index 52c61f0..f8dc7d7 100644
--- a/internal/imap/imap.go
+++ b/internal/imap/imap.go
@@ -10,53 +10,7 @@ import (
"github.com/Necoro/feed2imap-go/internal/log"
)
-const (
- imapsPort = "993"
- imapPort = "143"
- imapsSchema = "imaps"
- imapSchema = "imap"
-)
-
-func forceTLS(url *url.URL) bool {
- return url.Scheme == imapsSchema || url.Port() == imapsPort
-}
-
-func setDefaultScheme(url *url.URL) {
- switch url.Scheme {
- case imapSchema, imapsSchema:
- return
- default:
- oldScheme := url.Scheme
- if url.Port() == imapsPort {
- url.Scheme = imapsSchema
- } else {
- url.Scheme = imapSchema
- }
-
- if oldScheme != "" {
- log.Warnf("Unknown scheme '%s', defaulting to '%s'", oldScheme, url.Scheme)
- }
- }
-}
-
-func setDefaultPort(url *url.URL) {
- if url.Port() == "" {
- var port string
- if url.Scheme == imapsSchema {
- port = imapsPort
- } else {
- port = imapPort
- }
- url.Host += ":" + port
- }
-}
-
-func sanitizeUrl(url *url.URL) {
- setDefaultScheme(url)
- setDefaultPort(url)
-}
-
-func newImapClient(url *url.URL, forceTls bool) (*imapClient.Client,error) {
+func newImapClient(url *URL, forceTls bool) (*imapClient.Client,error) {
if forceTls {
c, err := imapClient.DialTLS(url.Host, nil)
if err != nil {
@@ -73,7 +27,7 @@ func newImapClient(url *url.URL, forceTls bool) (*imapClient.Client,error) {
}
}
-func (client *Client) connect(url *url.URL, forceTls bool) (*connection, error) {
+func (client *Client) connect(url *URL, forceTls bool) (*connection, error) {
c, err := newImapClient(url, forceTls)
if err != nil {
return nil, err
@@ -95,11 +49,10 @@ func (client *Client) connect(url *url.URL, forceTls bool) (*connection, error)
return conn, nil
}
-func Connect(url *url.URL) (*Client, error) {
+func Connect(_url *url.URL) (*Client, error) {
var err error
-
- sanitizeUrl(url)
- forceTls := forceTLS(url)
+ url := NewUrl(_url)
+ forceTls := url.ForceTLS()
client := NewClient()
client.host = url.Host
diff --git a/internal/imap/mailboxes.go b/internal/imap/mailboxes.go
new file mode 100644
index 0000000..d0fdede
--- /dev/null
+++ b/internal/imap/mailboxes.go
@@ -0,0 +1,34 @@
+package imap
+
+import (
+ "sync"
+
+ "github.com/emersion/go-imap"
+)
+
+type mailboxes struct {
+ mb map[string]*imap.MailboxInfo
+ mu sync.RWMutex
+}
+
+func (mbs *mailboxes) contains(elem Folder) bool {
+ mbs.mu.RLock()
+ defer mbs.mu.RUnlock()
+
+ _, ok := mbs.mb[elem.str]
+ return ok
+}
+
+func (mbs *mailboxes) add(elem *imap.MailboxInfo) {
+ mbs.mu.Lock()
+ defer mbs.mu.Unlock()
+
+ mbs.mb[elem.Name] = elem
+}
+
+func NewMailboxes() *mailboxes {
+ return &mailboxes{
+ mb: map[string]*imap.MailboxInfo{},
+ mu: sync.RWMutex{},
+ }
+} \ No newline at end of file
diff --git a/internal/imap/url.go b/internal/imap/url.go
new file mode 100644
index 0000000..6ffea72
--- /dev/null
+++ b/internal/imap/url.go
@@ -0,0 +1,76 @@
+package imap
+
+import (
+ "net"
+ "net/url"
+
+ "github.com/Necoro/feed2imap-go/internal/log"
+)
+
+// Our own convenience wrapper
+type URL struct {
+ *url.URL
+ // url.URL has no port field and splits it everytime from Host
+ port *string
+}
+
+const (
+ imapsPort = "993"
+ imapPort = "143"
+ imapsSchema = "imaps"
+ imapSchema = "imap"
+)
+
+func (url *URL) Port() string {
+ if url.port == nil {
+ port := url.URL.Port()
+ url.port = &port
+ }
+ return *url.port
+}
+
+func (url *URL) ForceTLS() bool {
+ return url.Scheme == imapsSchema || url.Port() == imapsPort
+}
+
+func (url *URL) setDefaultScheme() {
+ switch url.Scheme {
+ case imapSchema, imapsSchema:
+ return
+ default:
+ oldScheme := url.Scheme
+ if url.Port() == imapsPort {
+ url.Scheme = imapsSchema
+ } else {
+ url.Scheme = imapSchema
+ }
+
+ if oldScheme != "" {
+ log.Warnf("Unknown scheme '%s', defaulting to '%s'", oldScheme, url.Scheme)
+ }
+ }
+}
+
+func (url *URL) setDefaultPort() {
+ if url.Port() == "" {
+ var port string
+ if url.Scheme == imapsSchema {
+ port = imapsPort
+ } else {
+ port = imapPort
+ }
+ url.port = &port
+ url.Host = net.JoinHostPort(url.Host, port)
+ }
+}
+
+func (url *URL) sanitizeUrl() {
+ url.setDefaultScheme()
+ url.setDefaultPort()
+}
+
+func NewUrl(url *url.URL) *URL {
+ u := URL{URL: url}
+ u.sanitizeUrl()
+ return &u
+} \ No newline at end of file