212 行
5.4 KiB
Go
212 行
5.4 KiB
Go
package config
|
|
|
|
import (
|
|
"bufio"
|
|
"errors"
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"path"
|
|
"strings"
|
|
|
|
"git.sr.ht/~blallo/papero/worker"
|
|
"github.com/mitchellh/go-homedir"
|
|
)
|
|
|
|
var ErrPortOutOfRange = errors.New("port out of range")
|
|
var ErrMissingPassword = errors.New("password has not been set")
|
|
var ErrMissingDefaultAccount = errors.New("default_account is missing from config")
|
|
|
|
// CastingErrors is a simple wrapper around map[string]error. It's endowed with a utility
|
|
// function to check if there are any errors.
|
|
type CastingErrors struct {
|
|
GeneralErrors []error
|
|
AccountErrors map[string]error
|
|
}
|
|
|
|
func initCastingErrors() *CastingErrors {
|
|
c := CastingErrors{}
|
|
c.AccountErrors = make(map[string]error)
|
|
return &c
|
|
}
|
|
|
|
func (c *CastingErrors) appendGeneralError(e error) {
|
|
c.GeneralErrors = append(c.GeneralErrors, e)
|
|
}
|
|
|
|
func (c *CastingErrors) String() string {
|
|
out := "CastingErrors{"
|
|
out += fmt.Sprintf("GeneralErrors: %s, ", c.GeneralErrors)
|
|
out += fmt.Sprintf("AccountErrors: %s", c.AccountErrors)
|
|
out += "}"
|
|
return out
|
|
}
|
|
|
|
// Check if there are any errors, i.e. if any value of the map is not nil.
|
|
func (c CastingErrors) Check() bool {
|
|
if len(c.GeneralErrors) != 0 {
|
|
return false
|
|
}
|
|
for _, err := range c.GeneralErrors {
|
|
if err != nil {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
// MemConfig is the data structure that will be used program-wise. It holds all the
|
|
// necessary information to authenticate and manage each provided account.
|
|
type MemConfig struct {
|
|
Default string
|
|
Accounts map[string]*AccountData
|
|
Workers map[string]*worker.Worker
|
|
}
|
|
|
|
func initMemConfig() MemConfig {
|
|
m := MemConfig{}
|
|
m.Accounts = make(map[string]*AccountData)
|
|
m.Workers = make(map[string]*worker.Worker)
|
|
return m
|
|
}
|
|
|
|
// AccountData holds the data for the single account.
|
|
type AccountData struct {
|
|
Host string
|
|
Port int
|
|
Username string
|
|
Password string
|
|
ExcludedFolders []string
|
|
MailboxPath string
|
|
Messages int
|
|
}
|
|
|
|
func (a *AccountData) String() string {
|
|
var res string
|
|
res = "{"
|
|
res += fmt.Sprintf("Host: %s, ", a.Host)
|
|
res += fmt.Sprintf("Port: %d, ", a.Port)
|
|
res += fmt.Sprintf("Username: %s, ", a.Username)
|
|
res += fmt.Sprintf("Password: ********, ")
|
|
res += fmt.Sprintf("ExcludedFolders: %s, ", a.ExcludedFolders)
|
|
res += fmt.Sprintf("MailboxPath: %s}", a.MailboxPath)
|
|
res += fmt.Sprintf("Messages: %d}", a.Messages)
|
|
return res
|
|
}
|
|
|
|
// parseConfig translates a *fileConfig, as obtained from a file read, into a usable
|
|
// *MemConfig that could be used in computation, together with a custom *CastingErrors
|
|
// type, that holds the translation errors for each account.
|
|
func parseConfig(fileConfig *fileConfig) (*MemConfig, *CastingErrors) {
|
|
errors := initCastingErrors()
|
|
outConfig := initMemConfig()
|
|
defaultAccount := fileConfig.Default
|
|
if defaultAccount == "" {
|
|
errors.appendGeneralError(ErrMissingDefaultAccount)
|
|
}
|
|
outConfig.Default = defaultAccount
|
|
basePath, err := getOrDefaultMailbox(fileConfig.MailboxPath)
|
|
defaultMessages := getOrDefaultMessages(fileConfig.DefaultMessages)
|
|
if err != nil {
|
|
log.Fatal("Could not determine base path")
|
|
}
|
|
for _, account := range fileConfig.Accounts {
|
|
outConfig.Accounts[account.Name], errors.AccountErrors[account.Name] = parseData(&account, basePath, defaultMessages)
|
|
outConfig.Workers[account.Name] = &worker.Worker{}
|
|
}
|
|
return &outConfig, errors
|
|
}
|
|
|
|
func getOrDefaultMailbox(mailboxPath string) (string, error) {
|
|
if mailboxPath == "" {
|
|
if homePath, err := homedir.Dir(); err != nil {
|
|
return "", err
|
|
} else {
|
|
return path.Join(homePath, ".papero"), nil
|
|
}
|
|
}
|
|
return mailboxPath, nil
|
|
}
|
|
|
|
func getOrDefaultMessages(messages maybeInt) int {
|
|
if messages.empty() {
|
|
return 50
|
|
}
|
|
return messages.value()
|
|
}
|
|
|
|
func valueOrEmptySlice(value []string) []string {
|
|
if value == nil {
|
|
return []string{}
|
|
}
|
|
return value
|
|
}
|
|
|
|
func valueOrDefaultInt(value maybeInt, defaultVal int) int {
|
|
if value.empty() {
|
|
return defaultVal
|
|
}
|
|
return value.value()
|
|
}
|
|
|
|
func parseData(a *account, basePath string, defaultMessages int) (*AccountData, error) {
|
|
port, err := validatePort(a.ConnectionInfo.Port)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
password, err := getPassword(a.ConnectionInfo)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
accountData := AccountData{
|
|
Host: a.ConnectionInfo.Host,
|
|
Port: port,
|
|
Username: a.ConnectionInfo.Username,
|
|
Password: password,
|
|
ExcludedFolders: valueOrEmptySlice(a.ExcludedFolders),
|
|
MailboxPath: getMailboxPath(a.Name, a.MailboxPath, basePath),
|
|
Messages: valueOrDefaultInt(a.Messages, defaultMessages),
|
|
}
|
|
return &accountData, nil
|
|
}
|
|
|
|
func validatePort(port int) (int, error) {
|
|
if port > 0 && port < 2<<15 {
|
|
return port, nil
|
|
}
|
|
return 0, ErrPortOutOfRange
|
|
}
|
|
|
|
func getPassword(connection *connectionInfo) (string, error) {
|
|
if connection.PasswordExec.Present() {
|
|
pass, err := connection.PasswordExec.Run()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return strings.TrimRight(pass, "\n"), nil
|
|
}
|
|
if connection.PasswordFile != "" {
|
|
file, err := os.Open(connection.PasswordFile)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
reader := bufio.NewReader(file)
|
|
line, _, err := reader.ReadLine()
|
|
return string(line), err
|
|
}
|
|
if connection.Password == "" {
|
|
return "", ErrMissingPassword
|
|
}
|
|
return connection.Password, nil
|
|
}
|
|
|
|
func getMailboxPath(name, configPath, basePath string) string {
|
|
if configPath != "" {
|
|
if info, err := os.Stat(configPath); err == nil || err != os.ErrNotExist && info != nil {
|
|
if info.IsDir() {
|
|
return configPath
|
|
}
|
|
}
|
|
}
|
|
return path.Join(basePath, name)
|
|
}
|