-
Notifications
You must be signed in to change notification settings - Fork 1
/
main.go
260 lines (201 loc) · 5.39 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
package main
import (
"bytes"
"flag"
"fmt"
"os"
"os/exec"
"os/user"
"regexp"
"time"
"github.com/dnote/doctor/semver"
"github.com/dnote/fileutils"
"github.com/pkg/errors"
)
var (
homeDirPath = flag.String("homeDir", "", "the full path to the home directory")
)
const (
backupModeCopy = iota
backupModeRename
)
var versionTag = "master"
var helpText = `dnote-doctor
Automatically diagnose and fix any issues with local dnote copy.
Usage:
$ dnote-doctor
`
// Ctx holds runtime configuration of dnote doctor
type Ctx struct {
version semver.Version
homeDirPath string
dnoteDirPath string
}
func debug(msg string, v ...interface{}) {
if os.Getenv("DNOTE_DOCTOR_DEBUG") == "1" {
fmt.Printf("DEBUG: %s\n", fmt.Sprintf(msg, v...))
}
}
func getDnoteDirPath() string {
return fmt.Sprintf("%s/.dnote", *homeDirPath)
}
// backupDnoteDir backs up the dnote directory to a temporary backup directory
func backupDnoteDir(mode int) (string, error) {
dnoteDirPath := getDnoteDirPath()
backupName := fmt.Sprintf(".dnote-backup-%d", time.Now().UnixNano())
backupPath := fmt.Sprintf("%s/%s", *homeDirPath, backupName)
debug("backing up %s to %s", dnoteDirPath, backupPath)
var err error
switch mode {
case backupModeCopy:
err = fileutils.CopyDir(dnoteDirPath, backupPath)
case backupModeRename:
err = os.Rename(dnoteDirPath, backupPath)
}
if err != nil {
return backupPath, errors.Wrapf(err, "backing up %s using %d mode", dnoteDirPath, mode)
}
return backupPath, nil
}
func restoreBackup(backupPath string) error {
var err error
srcPath := getDnoteDirPath()
defer func() {
if err != nil {
fmt.Printf(`Failed to restore backup from dnote doctor.
Don't worry. Your data is still intact in the backup directory: %s
You can manually restore the backup by moving that directory to %s
Please reach out on https://github.com/dnote/cli/issues so that we can help you.
`, backupPath, srcPath)
}
}()
debug("restoring %s to %s", backupPath, srcPath)
if err = os.RemoveAll(srcPath); err != nil {
return errors.Wrapf(err, "Failed to clear current dnote data at %s", backupPath)
}
if err = os.Rename(backupPath, srcPath); err != nil {
return errors.Wrap(err, `Failed to copy backup data to the original directory.`)
}
return nil
}
func fixIssue(i issue, ctx Ctx) (bool, error) {
backupPath, err := backupDnoteDir(backupModeCopy)
if err != nil {
return false, errors.Wrap(err, "backing up dnote")
}
ok, err := i.fix(ctx)
if err != nil {
if e := restoreBackup(backupPath); e != nil {
panic(errors.Wrap(e, "restoring backup"))
}
return false, errors.Wrap(err, "diagnosing")
}
debug("fix complete. removing backup %s", backupPath)
if err := os.RemoveAll(backupPath); err != nil {
fmt.Println(errors.Wrapf(err, "could not remove the backup %s", backupPath))
}
return ok, nil
}
func scanIssues(version semver.Version) ([]issue, error) {
var ret []issue
for _, i := range issues {
if i.relevant(version) {
ret = append(ret, i)
}
}
return ret, nil
}
func checkVersion() (semver.Version, error) {
var ret semver.Version
backupPath, err := backupDnoteDir(backupModeRename)
if err != nil {
return ret, errors.Wrap(err, "backing up dnote")
}
cmd := exec.Command("dnote", "version")
var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stderr
if err := cmd.Run(); err != nil {
return ret, errors.Wrap(err, "running dnote version")
}
out := stdout.String()
r := regexp.MustCompile(`dnote (\d+\.\d+\.\d+)`)
matches := r.FindStringSubmatch(out)
if len(matches) == 0 {
return ret, errors.Errorf("unrecognized version output: %s", stdout.String())
}
v := matches[1]
ret, err = semver.Parse(v)
if err != nil {
return ret, errors.Wrap(err, "parsing semver")
}
err = restoreBackup(backupPath)
if err != nil {
return ret, errors.Wrap(err, "restoring backup")
}
return ret, nil
}
func parseFlag() error {
flag.Parse()
if *homeDirPath == "" {
usr, err := user.Current()
if err != nil {
return errors.Wrap(err, "getting the current user")
}
// set home dir
homeDirPath = &usr.HomeDir
}
return nil
}
func newCtx(version semver.Version) Ctx {
return Ctx{
version: version,
homeDirPath: *homeDirPath,
dnoteDirPath: fmt.Sprintf("%s/.dnote", *homeDirPath),
}
}
func main() {
if err := parseFlag(); err != nil {
panic(errors.Wrap(err, "parsing flag"))
}
args := os.Args
if len(args) > 1 {
cmd := args[1]
if cmd == "version" {
fmt.Printf("dnote-doctor %s\n", versionTag)
return
} else if cmd == "help" {
fmt.Println(helpText)
return
}
fmt.Printf("unknwon command %s\n", cmd)
return
}
version, err := checkVersion()
if err != nil {
panic(errors.Wrap(err, "checking version"))
}
debug("using version %d.%d.%d", version.Major, version.Minor, version.Patch)
issues, err := scanIssues(version)
if err != nil {
panic(errors.Wrap(err, "scanning issues"))
}
debug("%d issues apply to this version", len(issues))
ctx := newCtx(version)
for _, i := range issues {
fmt.Printf("diagnosing: %s...\n", i.name)
ok, err := fixIssue(i, ctx)
if err != nil {
fmt.Println(errors.Wrapf(err, "⨯ Failed to diagnose and fix %s", i.name))
fmt.Println(`Don't worry. Your data has not been affected.
Please reach out on https://github.com/dnote/cli/issues so that we can help you.`)
continue
}
if ok {
fmt.Println("✔ fixed")
} else {
fmt.Println("✔ no issue found")
}
}
fmt.Println("✔ done")
}