feat(PROJ-30): Xapian → Manticore Search Migration
- internal/index/manticore.go: ManticoreTenantManager + manticoreIndex (RT-Indizes, CGO-frei) - internal/index/index.go: TenantIndexer Interface (Xapian + Manticore) - internal/index/tenant_worker.go: mgr-Typ auf TenantIndexer Interface - internal/api/server.go: idxMgr auf TenantIndexer Interface - config/config.go: IndexConfig.ManticoreDSN Feld - cmd/archivmail/cmd_reindex.go: reindex Subkommando - cmd/archivmail/main.go: Manticore-Branch + reindex Case - go.mod: github.com/go-sql-driver/mysql v1.8.1 - update.sh: Manticore auto-install, CGO_ENABLED=0, config.yml migration, auto-reindex fix(IMAP): TCP-Deadline-Wrapper für steckengebliebene Imports fix(auth): Email-Claim in JWT für User-Isolation fix(search): User-Isolation via sess.Email (fail-safe) fix(ui): Admin-Login Auth-Cache, Logout-Redirect, IMAP-Polling-Resilienz Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+62
-16
@@ -3,17 +3,35 @@ package imap
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
imapv2 "github.com/emersion/go-imap/v2"
|
||||
"github.com/emersion/go-imap/v2/imapclient"
|
||||
)
|
||||
|
||||
// FolderInfo describes a single IMAP folder with exclusion metadata.
|
||||
type FolderInfo struct {
|
||||
Name string `json:"name"`
|
||||
Excluded bool `json:"excluded"`
|
||||
Reason string `json:"reason,omitempty"`
|
||||
const (
|
||||
dialTimeout = 30 * time.Second
|
||||
fetchTimeout = 5 * time.Minute // per-batch read/write deadline
|
||||
)
|
||||
|
||||
// Conn wraps an IMAP client with the underlying net.Conn so callers
|
||||
// can set per-operation deadlines to prevent indefinite blocking.
|
||||
type Conn struct {
|
||||
*imapclient.Client
|
||||
raw net.Conn
|
||||
}
|
||||
|
||||
// SetFetchDeadline sets a 5-minute read/write deadline on the connection.
|
||||
// Call this before each fetch batch to prevent stalled imports.
|
||||
func (c *Conn) SetFetchDeadline() {
|
||||
_ = c.raw.SetDeadline(time.Now().Add(fetchTimeout))
|
||||
}
|
||||
|
||||
// ClearDeadline removes any active deadline from the underlying connection.
|
||||
func (c *Conn) ClearDeadline() {
|
||||
_ = c.raw.SetDeadline(time.Time{})
|
||||
}
|
||||
|
||||
// junkTrashNames lists well-known junk/trash folder names for fallback detection.
|
||||
@@ -22,33 +40,61 @@ var junkTrashNames = []string{
|
||||
"deleted messages", "papierkorb", "gelöschte elemente",
|
||||
}
|
||||
|
||||
// FolderInfo describes a single IMAP folder with exclusion metadata.
|
||||
type FolderInfo struct {
|
||||
Name string `json:"name"`
|
||||
Excluded bool `json:"excluded"`
|
||||
Reason string `json:"reason,omitempty"`
|
||||
}
|
||||
|
||||
// Connect establishes an IMAP client connection using the specified TLS mode.
|
||||
func Connect(host string, port int, tlsMode string) (*imapclient.Client, error) {
|
||||
// Returns a Conn that exposes the underlying net.Conn for deadline management.
|
||||
func Connect(host string, port int, tlsMode string) (*Conn, error) {
|
||||
addr := fmt.Sprintf("%s:%d", host, port)
|
||||
|
||||
switch tlsMode {
|
||||
case "ssl":
|
||||
c, err := imapclient.DialTLS(addr, &imapclient.Options{
|
||||
TLSConfig: &tls.Config{ServerName: host},
|
||||
})
|
||||
dialer := &tls.Dialer{
|
||||
NetDialer: &net.Dialer{Timeout: dialTimeout},
|
||||
Config: &tls.Config{ServerName: host},
|
||||
}
|
||||
raw, err := dialer.Dial("tcp", addr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("imap connect ssl: %w", err)
|
||||
}
|
||||
return c, nil
|
||||
c, err := imapclient.New(raw, nil)
|
||||
if err != nil {
|
||||
raw.Close()
|
||||
return nil, fmt.Errorf("imap client ssl: %w", err)
|
||||
}
|
||||
return &Conn{Client: c, raw: raw}, nil
|
||||
|
||||
case "starttls":
|
||||
c, err := imapclient.DialStartTLS(addr, &imapclient.Options{
|
||||
TLSConfig: &tls.Config{ServerName: host},
|
||||
})
|
||||
raw, err := net.DialTimeout("tcp", addr, dialTimeout)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("imap connect starttls: %w", err)
|
||||
}
|
||||
return c, nil
|
||||
c, err := imapclient.New(raw, &imapclient.Options{
|
||||
TLSConfig: &tls.Config{ServerName: host},
|
||||
})
|
||||
if err != nil {
|
||||
raw.Close()
|
||||
return nil, fmt.Errorf("imap client starttls: %w", err)
|
||||
}
|
||||
return &Conn{Client: c, raw: raw}, nil
|
||||
|
||||
case "none":
|
||||
c, err := imapclient.DialInsecure(addr, nil)
|
||||
raw, err := net.DialTimeout("tcp", addr, dialTimeout)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("imap connect plain: %w", err)
|
||||
}
|
||||
return c, nil
|
||||
c, err := imapclient.New(raw, nil)
|
||||
if err != nil {
|
||||
raw.Close()
|
||||
return nil, fmt.Errorf("imap client plain: %w", err)
|
||||
}
|
||||
return &Conn{Client: c, raw: raw}, nil
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("imap: unknown tls mode %q", tlsMode)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user