config.go 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. package config
  2. import (
  3. "encoding/json"
  4. "errors"
  5. "fmt"
  6. "io/ioutil"
  7. "os/user"
  8. "path/filepath"
  9. "strconv"
  10. "github.com/asaskevich/govalidator"
  11. "github.com/claudiodangelis/qrcp/util"
  12. "github.com/manifoldco/promptui"
  13. )
  14. // Config of qrcp
  15. type Config struct {
  16. FQDN string `json:"fqdn"`
  17. Interface string `json:"interface"`
  18. Port int `json:"port"`
  19. KeepAlive bool `json:"keepAlive"`
  20. Path string `json:"path"`
  21. }
  22. func configFile() string {
  23. currentUser, err := user.Current()
  24. if err != nil {
  25. panic(err)
  26. }
  27. return filepath.Join(currentUser.HomeDir, ".qrcp.json")
  28. }
  29. type configOptions struct {
  30. interactive bool
  31. listAllInterfaces bool
  32. }
  33. func chooseInterface(opts configOptions) (string, error) {
  34. interfaces, err := util.Interfaces(opts.listAllInterfaces)
  35. if err != nil {
  36. return "", err
  37. }
  38. if len(interfaces) == 0 {
  39. return "", errors.New("no interfaces found")
  40. }
  41. if len(interfaces) == 1 && opts.interactive == false {
  42. for name := range interfaces {
  43. fmt.Printf("only one interface found: %s, using this one\n", name)
  44. return name, nil
  45. }
  46. }
  47. // Map for pretty printing
  48. m := make(map[string]string)
  49. items := []string{}
  50. for name, ip := range interfaces {
  51. label := fmt.Sprintf("%s (%s)", name, ip)
  52. m[label] = name
  53. items = append(items, label)
  54. }
  55. // Add the "any" interface
  56. anyIP := "0.0.0.0"
  57. anyName := "any"
  58. anyLabel := fmt.Sprintf("%s (%s)", anyName, anyIP)
  59. m[anyLabel] = anyName
  60. items = append(items, anyLabel)
  61. prompt := promptui.Select{
  62. Items: items,
  63. Label: "Choose interface",
  64. }
  65. _, result, err := prompt.Run()
  66. if err != nil {
  67. return "", err
  68. }
  69. return m[result], nil
  70. }
  71. // Load a new configuration
  72. func Load(opts configOptions) (Config, error) {
  73. var cfg Config
  74. // Read the configuration file, if it exists
  75. if file, err := ioutil.ReadFile(configFile()); err == nil {
  76. // Read the config
  77. if err := json.Unmarshal(file, &cfg); err != nil {
  78. return cfg, err
  79. }
  80. }
  81. // Prompt if needed
  82. if cfg.Interface == "" {
  83. iface, err := chooseInterface(opts)
  84. if err != nil {
  85. return cfg, err
  86. }
  87. cfg.Interface = iface
  88. // Write config
  89. if err := write(cfg); err != nil {
  90. return cfg, err
  91. }
  92. }
  93. return cfg, nil
  94. }
  95. // Wizard starts an interactive configuration managements
  96. func Wizard() error {
  97. var cfg Config
  98. if file, err := ioutil.ReadFile(configFile()); err == nil {
  99. // Read the config
  100. if err := json.Unmarshal(file, &cfg); err != nil {
  101. return err
  102. }
  103. }
  104. // Ask for interface
  105. opts := configOptions{
  106. interactive: true,
  107. }
  108. iface, err := chooseInterface(opts)
  109. if err != nil {
  110. return err
  111. }
  112. cfg.Interface = iface
  113. // Ask for fully qualified domain name
  114. validateFqdn := func(input string) error {
  115. if input != "" && govalidator.IsDNSName(input) == false {
  116. return errors.New("invalid domain")
  117. }
  118. return nil
  119. }
  120. promptFqdn := promptui.Prompt{
  121. Validate: validateFqdn,
  122. Label: "Choose fully-qualified domain name",
  123. Default: "",
  124. }
  125. if promptFqdnString, err := promptFqdn.Run(); err == nil {
  126. cfg.FQDN = promptFqdnString
  127. }
  128. // Ask for port
  129. validatePort := func(input string) error {
  130. _, err := strconv.ParseInt(input, 10, 16)
  131. if err != nil {
  132. return errors.New("Invalid number")
  133. }
  134. return nil
  135. }
  136. promptPort := promptui.Prompt{
  137. Validate: validatePort,
  138. Label: "Choose port, 0 means random port",
  139. Default: fmt.Sprintf("%d", cfg.Port),
  140. }
  141. if promptPortResultString, err := promptPort.Run(); err == nil {
  142. if port, err := strconv.ParseInt(promptPortResultString, 10, 16); err == nil {
  143. cfg.Port = int(port)
  144. }
  145. }
  146. // Ask for path
  147. promptPath := promptui.Prompt{
  148. Label: "Choose path, empty means random",
  149. Default: cfg.Path,
  150. }
  151. if promptPathResultString, err := promptPath.Run(); err == nil {
  152. if promptPathResultString != "" {
  153. cfg.Path = promptPathResultString
  154. }
  155. }
  156. // Ask for keep alive
  157. promptKeepAlive := promptui.Select{
  158. Items: []string{"No", "Yes"},
  159. Label: "Should the server keep alive after transferring?",
  160. }
  161. if _, promptKeepAliveResultString, err := promptKeepAlive.Run(); err == nil {
  162. if promptKeepAliveResultString == "Yes" {
  163. cfg.KeepAlive = true
  164. } else {
  165. cfg.KeepAlive = false
  166. }
  167. }
  168. // Write it down
  169. if err := write(cfg); err != nil {
  170. return err
  171. }
  172. b, err := json.MarshalIndent(cfg, "", " ")
  173. if err != nil {
  174. return err
  175. }
  176. fmt.Printf("Configuration updated:\n%s\n", string(b))
  177. return nil
  178. }
  179. // write the configuration file to disk
  180. func write(cfg Config) error {
  181. j, err := json.MarshalIndent(cfg, "", " ")
  182. if err != nil {
  183. return err
  184. }
  185. if err := ioutil.WriteFile(configFile(), j, 0644); err != nil {
  186. return err
  187. }
  188. return nil
  189. }
  190. // New returns a new configuration struct. It loads defaults, then overrides
  191. // values if any.
  192. func New(iface string, port int, path string, fqdn string, keepAlive bool, listAllInterfaces bool) (Config, error) {
  193. // Load saved file / defults
  194. cfg, err := Load(configOptions{listAllInterfaces: listAllInterfaces})
  195. if err != nil {
  196. return cfg, err
  197. }
  198. if iface != "" {
  199. cfg.Interface = iface
  200. }
  201. if fqdn != "" {
  202. if govalidator.IsDNSName(fqdn) == false {
  203. return cfg, errors.New("invalid value for fully-qualified domain name")
  204. }
  205. cfg.FQDN = fqdn
  206. }
  207. if port != 0 {
  208. if port > 65535 {
  209. return cfg, errors.New("invalid value for port")
  210. }
  211. cfg.Port = port
  212. }
  213. if keepAlive {
  214. cfg.KeepAlive = true
  215. }
  216. if path != "" {
  217. cfg.Path = path
  218. }
  219. return cfg, nil
  220. }