1 package driver
2
3 import (
4 "encoding/json"
5 "fmt"
6 "net/url"
7 "os"
8 "path/filepath"
9 )
10
11
12 type settings struct {
13
14 Configs []namedConfig `json:"configs"`
15 }
16
17
18 type namedConfig struct {
19 Name string `json:"name"`
20 config
21 }
22
23
24 func settingsFileName() (string, error) {
25
26 dir, err := os.UserConfigDir()
27 if err != nil {
28 return "", err
29 }
30 return filepath.Join(dir, "pprof", "settings.json"), nil
31 }
32
33
34 func readSettings(fname string) (*settings, error) {
35 data, err := os.ReadFile(fname)
36 if err != nil {
37 if os.IsNotExist(err) {
38 return &settings{}, nil
39 }
40 return nil, fmt.Errorf("could not read settings: %w", err)
41 }
42 settings := &settings{}
43 if err := json.Unmarshal(data, settings); err != nil {
44 return nil, fmt.Errorf("could not parse settings: %w", err)
45 }
46 for i := range settings.Configs {
47 settings.Configs[i].resetTransient()
48 }
49 return settings, nil
50 }
51
52
53 func writeSettings(fname string, settings *settings) error {
54 data, err := json.MarshalIndent(settings, "", " ")
55 if err != nil {
56 return fmt.Errorf("could not encode settings: %w", err)
57 }
58
59
60
61
62 if err := os.MkdirAll(filepath.Dir(fname), 0700); err != nil {
63 return fmt.Errorf("failed to create settings directory: %w", err)
64 }
65
66 if err := os.WriteFile(fname, data, 0644); err != nil {
67 return fmt.Errorf("failed to write settings: %w", err)
68 }
69 return nil
70 }
71
72
73 type configMenuEntry struct {
74 Name string
75 URL string
76 Current bool
77 UserConfig bool
78 }
79
80
81 func configMenu(fname string, u url.URL) []configMenuEntry {
82
83 configs := []namedConfig{{Name: "Default", config: defaultConfig()}}
84 if settings, err := readSettings(fname); err == nil {
85
86 configs = append(configs, settings.Configs...)
87 }
88
89
90 result := make([]configMenuEntry, len(configs))
91 lastMatch := -1
92 for i, cfg := range configs {
93 dst, changed := cfg.config.makeURL(u)
94 if !changed {
95 lastMatch = i
96 }
97
98 rel := &url.URL{RawQuery: dst.RawQuery, ForceQuery: true}
99 result[i] = configMenuEntry{
100 Name: cfg.Name,
101 URL: rel.String(),
102 UserConfig: (i != 0),
103 }
104 }
105
106 if lastMatch >= 0 {
107 result[lastMatch].Current = true
108 }
109 return result
110 }
111
112
113 func editSettings(fname string, fn func(s *settings) error) error {
114 settings, err := readSettings(fname)
115 if err != nil {
116 return err
117 }
118 if err := fn(settings); err != nil {
119 return err
120 }
121 return writeSettings(fname, settings)
122 }
123
124
125 func setConfig(fname string, request url.URL) error {
126 q := request.Query()
127 name := q.Get("config")
128 if name == "" {
129 return fmt.Errorf("invalid config name")
130 }
131 cfg := currentConfig()
132 if err := cfg.applyURL(q); err != nil {
133 return err
134 }
135 return editSettings(fname, func(s *settings) error {
136 for i, c := range s.Configs {
137 if c.Name == name {
138 s.Configs[i].config = cfg
139 return nil
140 }
141 }
142 s.Configs = append(s.Configs, namedConfig{Name: name, config: cfg})
143 return nil
144 })
145 }
146
147
148 func removeConfig(fname, config string) error {
149 return editSettings(fname, func(s *settings) error {
150 for i, c := range s.Configs {
151 if c.Name == config {
152 s.Configs = append(s.Configs[:i], s.Configs[i+1:]...)
153 return nil
154 }
155 }
156 return fmt.Errorf("config %s not found", config)
157 })
158 }
159
View as plain text