config.go 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. package config
  2. import (
  3. "bufio"
  4. "errors"
  5. "fmt"
  6. "log"
  7. "os"
  8. "path"
  9. "strings"
  10. "git.sr.ht/~blallo/papero/worker"
  11. "github.com/mitchellh/go-homedir"
  12. )
  13. var ErrPortOutOfRange = errors.New("port out of range")
  14. var ErrMissingPassword = errors.New("password has not been set")
  15. var ErrMissingDefaultAccount = errors.New("default_account is missing from config")
  16. // CastingErrors is a simple wrapper around map[string]error. It's endowed with a utility
  17. // function to check if there are any errors.
  18. type CastingErrors struct {
  19. GeneralErrors []error
  20. AccountErrors map[string]error
  21. }
  22. func initCastingErrors() *CastingErrors {
  23. c := CastingErrors{}
  24. c.AccountErrors = make(map[string]error)
  25. return &c
  26. }
  27. func (c *CastingErrors) appendGeneralError(e error) {
  28. c.GeneralErrors = append(c.GeneralErrors, e)
  29. }
  30. func (c *CastingErrors) String() string {
  31. out := "CastingErrors{"
  32. out += fmt.Sprintf("GeneralErrors: %s, ", c.GeneralErrors)
  33. out += fmt.Sprintf("AccountErrors: %s", c.AccountErrors)
  34. out += "}"
  35. return out
  36. }
  37. // Check if there are any errors, i.e. if any value of the map is not nil.
  38. func (c CastingErrors) Check() bool {
  39. if len(c.GeneralErrors) != 0 {
  40. return false
  41. }
  42. for _, err := range c.GeneralErrors {
  43. if err != nil {
  44. return false
  45. }
  46. }
  47. return true
  48. }
  49. // MemConfig is the data structure that will be used program-wise. It holds all the
  50. // necessary information to authenticate and manage each provided account.
  51. type MemConfig struct {
  52. Default string
  53. Accounts map[string]*AccountData
  54. Workers map[string]*worker.Worker
  55. }
  56. func initMemConfig() MemConfig {
  57. m := MemConfig{}
  58. m.Accounts = make(map[string]*AccountData)
  59. m.Workers = make(map[string]*worker.Worker)
  60. return m
  61. }
  62. // AccountData holds the data for the single account.
  63. type AccountData struct {
  64. Host string
  65. Port int
  66. Username string
  67. Password string
  68. ExcludedFolders []string
  69. MailboxPath string
  70. Messages int
  71. }
  72. func (a *AccountData) String() string {
  73. var res string
  74. res = "{"
  75. res += fmt.Sprintf("Host: %s, ", a.Host)
  76. res += fmt.Sprintf("Port: %d, ", a.Port)
  77. res += fmt.Sprintf("Username: %s, ", a.Username)
  78. res += fmt.Sprintf("Password: ********, ")
  79. res += fmt.Sprintf("ExcludedFolders: %s, ", a.ExcludedFolders)
  80. res += fmt.Sprintf("MailboxPath: %s}", a.MailboxPath)
  81. res += fmt.Sprintf("Messages: %d}", a.Messages)
  82. return res
  83. }
  84. // parseConfig translates a *fileConfig, as obtained from a file read, into a usable
  85. // *MemConfig that could be used in computation, together with a custom *CastingErrors
  86. // type, that holds the translation errors for each account.
  87. func parseConfig(fileConfig *fileConfig) (*MemConfig, *CastingErrors) {
  88. errors := initCastingErrors()
  89. outConfig := initMemConfig()
  90. defaultAccount := fileConfig.Default
  91. if defaultAccount == "" {
  92. errors.appendGeneralError(ErrMissingDefaultAccount)
  93. }
  94. outConfig.Default = defaultAccount
  95. basePath, err := getOrDefaultMailbox(fileConfig.MailboxPath)
  96. defaultMessages := getOrDefaultMessages(fileConfig.DefaultMessages)
  97. if err != nil {
  98. log.Fatal("Could not determine base path")
  99. }
  100. for _, account := range fileConfig.Accounts {
  101. outConfig.Accounts[account.Name], errors.AccountErrors[account.Name] = parseData(&account, basePath, defaultMessages)
  102. outConfig.Workers[account.Name] = &worker.Worker{}
  103. }
  104. return &outConfig, errors
  105. }
  106. func getOrDefaultMailbox(mailboxPath string) (string, error) {
  107. if mailboxPath == "" {
  108. if homePath, err := homedir.Dir(); err != nil {
  109. return "", err
  110. } else {
  111. return path.Join(homePath, ".papero"), nil
  112. }
  113. }
  114. return mailboxPath, nil
  115. }
  116. func getOrDefaultMessages(messages maybeInt) int {
  117. if messages.empty() {
  118. return 50
  119. }
  120. return messages.value()
  121. }
  122. func valueOrEmptySlice(value []string) []string {
  123. if value == nil {
  124. return []string{}
  125. }
  126. return value
  127. }
  128. func valueOrDefaultInt(value maybeInt, defaultVal int) int {
  129. if value.empty() {
  130. return defaultVal
  131. }
  132. return value.value()
  133. }
  134. func parseData(a *account, basePath string, defaultMessages int) (*AccountData, error) {
  135. port, err := validatePort(a.ConnectionInfo.Port)
  136. if err != nil {
  137. return nil, err
  138. }
  139. password, err := getPassword(a.ConnectionInfo)
  140. if err != nil {
  141. return nil, err
  142. }
  143. accountData := AccountData{
  144. Host: a.ConnectionInfo.Host,
  145. Port: port,
  146. Username: a.ConnectionInfo.Username,
  147. Password: password,
  148. ExcludedFolders: valueOrEmptySlice(a.ExcludedFolders),
  149. MailboxPath: getMailboxPath(a.Name, a.MailboxPath, basePath),
  150. Messages: valueOrDefaultInt(a.Messages, defaultMessages),
  151. }
  152. return &accountData, nil
  153. }
  154. func validatePort(port int) (int, error) {
  155. if port > 0 && port < 2<<15 {
  156. return port, nil
  157. }
  158. return 0, ErrPortOutOfRange
  159. }
  160. func getPassword(connection *connectionInfo) (string, error) {
  161. if connection.PasswordExec.Present() {
  162. pass, err := connection.PasswordExec.Run()
  163. if err != nil {
  164. return "", err
  165. }
  166. return strings.TrimRight(pass, "\n"), nil
  167. }
  168. if connection.PasswordFile != "" {
  169. file, err := os.Open(connection.PasswordFile)
  170. if err != nil {
  171. return "", err
  172. }
  173. reader := bufio.NewReader(file)
  174. line, _, err := reader.ReadLine()
  175. return string(line), err
  176. }
  177. if connection.Password == "" {
  178. return "", ErrMissingPassword
  179. }
  180. return connection.Password, nil
  181. }
  182. func getMailboxPath(name, configPath, basePath string) string {
  183. if configPath != "" {
  184. if info, err := os.Stat(configPath); err == nil || err != os.ErrNotExist && info != nil {
  185. if info.IsDir() {
  186. return configPath
  187. }
  188. }
  189. }
  190. return path.Join(basePath, name)
  191. }