Bez popisu

settings.go 4.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. package driver
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "io/ioutil"
  6. "net/url"
  7. "os"
  8. "path/filepath"
  9. )
  10. // settings holds pprof settings.
  11. type settings struct {
  12. // Configs holds a list of named UI configurations.
  13. Configs []namedConfig `json:"configs"`
  14. }
  15. // namedConfig associates a name with a config.
  16. type namedConfig struct {
  17. Name string `json:"name"`
  18. config
  19. }
  20. // settingsFileName returns the name of the file where settings should be saved.
  21. func settingsFileName() (string, error) {
  22. // Return "pprof/settings.json" under os.UserConfigDir().
  23. dir, err := os.UserConfigDir()
  24. if err != nil {
  25. return "", err
  26. }
  27. return filepath.Join(dir, "pprof", "settings.json"), nil
  28. }
  29. // readSettings reads settings from fname.
  30. func readSettings(fname string) (*settings, error) {
  31. data, err := ioutil.ReadFile(fname)
  32. if err != nil {
  33. if os.IsNotExist(err) {
  34. return &settings{}, nil
  35. }
  36. return nil, fmt.Errorf("could not read settings: %w", err)
  37. }
  38. settings := &settings{}
  39. if err := json.Unmarshal(data, settings); err != nil {
  40. return nil, fmt.Errorf("could not parse settings: %w", err)
  41. }
  42. for i := range settings.Configs {
  43. settings.Configs[i].resetTransient()
  44. }
  45. return settings, nil
  46. }
  47. // writeSettings saves settings to fname.
  48. func writeSettings(fname string, settings *settings) error {
  49. data, err := json.MarshalIndent(settings, "", " ")
  50. if err != nil {
  51. return fmt.Errorf("could not encode settings: %w", err)
  52. }
  53. // create the settings directory if it does not exist
  54. // XDG specifies permissions 0700 when creating settings dirs:
  55. // https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
  56. if err := os.MkdirAll(filepath.Dir(fname), 0700); err != nil {
  57. return fmt.Errorf("failed to create settings directory: %w", err)
  58. }
  59. if err := ioutil.WriteFile(fname, data, 0644); err != nil {
  60. return fmt.Errorf("failed to write settings: %w", err)
  61. }
  62. return nil
  63. }
  64. // configMenuEntry holds information for a single config menu entry.
  65. type configMenuEntry struct {
  66. Name string
  67. URL string
  68. Current bool // Is this the currently selected config?
  69. UserConfig bool // Is this a user-provided config?
  70. }
  71. // configMenu returns a list of items to add to a menu in the web UI.
  72. func configMenu(fname string, url url.URL) []configMenuEntry {
  73. // Start with system configs.
  74. configs := []namedConfig{{Name: "Default", config: defaultConfig()}}
  75. if settings, err := readSettings(fname); err == nil {
  76. // Add user configs.
  77. configs = append(configs, settings.Configs...)
  78. }
  79. // Convert to menu entries.
  80. result := make([]configMenuEntry, len(configs))
  81. lastMatch := -1
  82. for i, cfg := range configs {
  83. dst, changed := cfg.config.makeURL(url)
  84. if !changed {
  85. lastMatch = i
  86. }
  87. result[i] = configMenuEntry{
  88. Name: cfg.Name,
  89. URL: dst.String(),
  90. UserConfig: (i != 0),
  91. }
  92. }
  93. // Mark the last matching config as currennt
  94. if lastMatch >= 0 {
  95. result[lastMatch].Current = true
  96. }
  97. return result
  98. }
  99. // editSettings edits settings by applying fn to them.
  100. func editSettings(fname string, fn func(s *settings) error) error {
  101. settings, err := readSettings(fname)
  102. if err != nil {
  103. return err
  104. }
  105. if err := fn(settings); err != nil {
  106. return err
  107. }
  108. return writeSettings(fname, settings)
  109. }
  110. // setConfig saves the config specified in request to fname.
  111. func setConfig(fname string, request url.URL) error {
  112. q := request.Query()
  113. name := q.Get("config")
  114. if name == "" {
  115. return fmt.Errorf("invalid config name")
  116. }
  117. cfg := currentConfig()
  118. if err := cfg.applyURL(q); err != nil {
  119. return err
  120. }
  121. return editSettings(fname, func(s *settings) error {
  122. for i, c := range s.Configs {
  123. if c.Name == name {
  124. s.Configs[i].config = cfg
  125. return nil
  126. }
  127. }
  128. s.Configs = append(s.Configs, namedConfig{Name: name, config: cfg})
  129. return nil
  130. })
  131. }
  132. // removeConfig removes config from fname.
  133. func removeConfig(fname, config string) error {
  134. return editSettings(fname, func(s *settings) error {
  135. for i, c := range s.Configs {
  136. if c.Name == config {
  137. s.Configs = append(s.Configs[:i], s.Configs[i+1:]...)
  138. return nil
  139. }
  140. }
  141. return fmt.Errorf("config %s not found", config)
  142. })
  143. }