-
Notifications
You must be signed in to change notification settings - Fork 1
/
serve.go
178 lines (146 loc) · 3.63 KB
/
serve.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
package main
import (
"errors"
"fmt"
"io"
"log"
"os"
"os/exec"
"os/signal"
"syscall"
"github.com/robfig/cron/v3"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
var serveCmd = &cobra.Command{
Use: "serve",
Short: "serve arxiv-sanity-lite",
Long: `Run arxiv-sanity-lite along with all of its supporting scheduled operations.`,
Args: cobra.NoArgs,
Run: func(cmd *cobra.Command, args []string) {
setConfig()
if viper.GetString("secretkey") == "devkey" {
log.Println("WARNING: Using default secret key. Please set the SECRETKEY env var")
}
if err := serve(); err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
}
},
}
func serve() error {
// if /data doesn't exist or is empty, create it and then poll for the first time
fromScratch, err := noData("/data")
if err != nil {
return fmt.Errorf("check database: %w", err)
}
// set the secret key
err = os.WriteFile("secret_key.txt", []byte(viper.GetString("secretkey")), 0o600)
if err != nil {
return fmt.Errorf("set secret key: %w", err)
}
log.Println("starting database from scratch:", fromScratch)
if fromScratch {
// create the directory if it doesn't exist
err = os.MkdirAll("/data", 0o777)
if err != nil {
return err
}
// poll for the first time
err = pollAndCompute()
if err != nil {
return err
}
}
// schedule periodic tasks
log.Println("scheduling tasks")
scheduler := cron.New()
err = schedule(scheduler)
if err != nil {
return err
}
scheduler.Start()
// catch shutdown signals
exitsignal := make(chan os.Signal, 1)
signal.Notify(exitsignal, syscall.SIGINT, syscall.SIGTERM)
// start server
go func() {
cmd := exec.Command(
"flask", "run",
"--port=80", "--host=0.0.0.0", // necessary for docker
)
cmd.Env = []string{
"FLASK_APP=serve.py",
"PYTHONUNBUFFERED=1", // so we can get Python print statements
}
log.Println("starting web server")
err := run(cmd)
if err != nil {
fmt.Printf("error from web server: %v\n", err)
exitsignal <- syscall.SIGINT
}
}()
// wait for signal or server error
<-exitsignal
// wait for any running jobs to finish
context := scheduler.Stop()
<-context.Done()
return nil
}
// noData returns true if the data directory doesn't exist or if it's empty.
func noData(path string) (bool, error) {
dir, err := isDir(path)
if err != nil || !dir {
return true, err
}
f, err := os.Open(path)
if err != nil {
return false, err
}
defer f.Close()
_, err = f.Readdirnames(1)
if errors.Is(err, io.EOF) {
return true, nil
} else if errors.Is(err, os.ErrNotExist) {
return true, nil
}
return false, err
}
// isDir returns true if the path exists and is a directory.
func isDir(path string) (bool, error) {
fi, err := os.Stat(path)
if err != nil {
if os.IsNotExist(err) {
return false, nil
}
return false, err
}
return fi.IsDir(), nil
}
func schedule(scheduler *cron.Cron) error {
// schedule arXiv polling
if viper.GetBool("poll.enabled") {
_, err := scheduler.AddFunc(viper.GetString("poll.cron"), func() {
log.Println("polling arXiv")
err := pollAndCompute()
if err != nil {
log.Printf("error while polling: %v\n", err)
}
})
if err != nil {
return fmt.Errorf("invalid polling cron string: %w", err)
}
}
// schedule mail
if viper.GetBool("mail.enabled") {
log.Println("FATAL: mail is not implemented yet, but it is enabled")
_, err := scheduler.AddFunc(viper.GetString("mail.cron"), func() {
log.Println("sending mail")
log.Println("sent absolutely ZERO mail because it's not implemented!")
})
if err != nil {
return fmt.Errorf("invalid mail cron string: %w", err)
}
}
return nil
}