|
@@ -0,0 +1,177 @@
|
|
|
+package config
|
|
|
+
|
|
|
+import (
|
|
|
+ "bufio"
|
|
|
+ "errors"
|
|
|
+ "fmt"
|
|
|
+ "log"
|
|
|
+ "os"
|
|
|
+ "path"
|
|
|
+
|
|
|
+ "git.lattuga.net/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")
|
|
|
+
|
|
|
+// 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 map[string]error
|
|
|
+
|
|
|
+// Check if there are any errors, i.e. if any value of the map is not nil.
|
|
|
+func (c CastingErrors) Check() bool {
|
|
|
+ for _, err := range c {
|
|
|
+ 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 {
|
|
|
+ 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) {
|
|
|
+ outConfig := initMemConfig()
|
|
|
+ basePath, err := getOrDefaultMailbox(fileConfig.MailboxPath)
|
|
|
+ defaultMessages := getOrDefaultMessages(fileConfig.DefaultMessages)
|
|
|
+ if err != nil {
|
|
|
+ log.Fatal("Could not determine base path")
|
|
|
+ }
|
|
|
+ errors := make(map[string]error)
|
|
|
+ for _, account := range fileConfig.Accounts {
|
|
|
+ outConfig.Accounts[account.Name], errors[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() {
|
|
|
+ return connection.PasswordExec.Run()
|
|
|
+ }
|
|
|
+ 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 {
|
|
|
+ log.Printf("name: %s,\tconfig_path: %s,\tbase_path: %s\n", name, configPath, basePath)
|
|
|
+ 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)
|
|
|
+}
|