blob: 93e78c42da266a20264e2c46da316d50dfa5b277 [file] [log] [blame]
commit-bot@chromium.org6d036c22014-04-09 18:59:44 +00001package main
2
3import (
4 "bytes"
5 "crypto/md5"
commit-bot@chromium.org282333f2014-04-14 14:54:07 +00006 "database/sql"
commit-bot@chromium.org6d036c22014-04-09 18:59:44 +00007 "encoding/base64"
commit-bot@chromium.org0a7e5b72014-05-29 15:58:00 +00008 "encoding/binary"
commit-bot@chromium.org6d036c22014-04-09 18:59:44 +00009 "encoding/json"
10 "flag"
11 "fmt"
commit-bot@chromium.orgc81d1c42014-04-14 18:53:10 +000012 htemplate "html/template"
commit-bot@chromium.org0a7e5b72014-05-29 15:58:00 +000013 "image"
14 _ "image/gif"
15 _ "image/jpeg"
16 "image/png"
commit-bot@chromium.org6d036c22014-04-09 18:59:44 +000017 "io/ioutil"
18 "log"
commit-bot@chromium.orgd04e1dd2014-04-19 13:55:50 +000019 "math/rand"
jcgregorio65998812014-06-06 06:30:31 -070020 "net"
commit-bot@chromium.org6d036c22014-04-09 18:59:44 +000021 "net/http"
22 "os"
23 "os/exec"
24 "path/filepath"
commit-bot@chromium.orgc81d1c42014-04-14 18:53:10 +000025 "regexp"
commit-bot@chromium.org6d036c22014-04-09 18:59:44 +000026 "strings"
27 "text/template"
commit-bot@chromium.org06aca012014-04-14 20:12:08 +000028 "time"
commit-bot@chromium.org6d036c22014-04-09 18:59:44 +000029)
30
commit-bot@chromium.orga4c36772014-05-19 17:38:52 +000031import (
32 "github.com/fiorix/go-web/autogzip"
33 _ "github.com/go-sql-driver/mysql"
34 _ "github.com/mattn/go-sqlite3"
jcgregorio65998812014-06-06 06:30:31 -070035 "github.com/rcrowley/go-metrics"
commit-bot@chromium.orga4c36772014-05-19 17:38:52 +000036)
37
commit-bot@chromium.org6d036c22014-04-09 18:59:44 +000038const (
commit-bot@chromium.org89126a62014-04-30 19:38:51 +000039 RESULT_COMPILE = `../../experimental/webtry/safec++ -DSK_GAMMA_SRGB -DSK_GAMMA_APPLY_TO_A8 -DSK_SCALAR_TO_FLOAT_EXCLUDED -DSK_ALLOW_STATIC_GLOBAL_INITIALIZERS=1 -DSK_SUPPORT_GPU=0 -DSK_SUPPORT_OPENCL=0 -DSK_FORCE_DISTANCEFIELD_FONTS=0 -DSK_SCALAR_IS_FLOAT -DSK_CAN_USE_FLOAT -DSK_SAMPLES_FOR_X -DSK_BUILD_FOR_UNIX -DSK_USE_POSIX_THREADS -DSK_SYSTEM_ZLIB=1 -DSK_DEBUG -DSK_DEVELOPER=1 -I../../src/core -I../../src/images -I../../tools/flags -I../../include/config -I../../include/core -I../../include/pathops -I../../include/pipe -I../../include/effects -I../../include/ports -I../../src/sfnt -I../../include/utils -I../../src/utils -I../../include/images -g -fno-exceptions -fstrict-aliasing -Wall -Wextra -Winit-self -Wpointer-arith -Wno-unused-parameter -m64 -fno-rtti -Wnon-virtual-dtor -c ../../../cache/%s.cpp -o ../../../cache/%s.o`
jcgregorio65998812014-06-06 06:30:31 -070040 LINK = `../../experimental/webtry/safec++ -m64 -lstdc++ -lm -o ../../../inout/%s -Wl,--start-group ../../../cache/%s.o obj/experimental/webtry/webtry.main.o obj/gyp/libflags.a libskia_images.a libskia_core.a libskia_effects.a obj/gyp/libjpeg.a obj/gyp/libetc1.a obj/gyp/libSkKTX.a obj/gyp/libwebp_dec.a obj/gyp/libwebp_demux.a obj/gyp/libwebp_dsp.a obj/gyp/libwebp_enc.a obj/gyp/libwebp_utils.a libskia_utils.a libskia_opts.a libskia_opts_ssse3.a libskia_ports.a libskia_sfnt.a -Wl,--end-group -lpng -lz -lgif -lpthread -lfontconfig -ldl -lfreetype`
jcgregorio92bfb092014-06-03 09:04:44 -070041
fmalita@google.com950306c2014-05-01 15:14:56 +000042 DEFAULT_SAMPLE = `void draw(SkCanvas* canvas) {
43 SkPaint p;
44 p.setColor(SK_ColorRED);
45 p.setAntiAlias(true);
46 p.setStyle(SkPaint::kStroke_Style);
47 p.setStrokeWidth(10);
commit-bot@chromium.orgc81d1c42014-04-14 18:53:10 +000048
fmalita@google.com950306c2014-05-01 15:14:56 +000049 canvas->drawLine(20, 20, 100, 100, p);
50}`
commit-bot@chromium.org4bd8fdc2014-04-15 00:43:51 +000051 // Don't increase above 2^16 w/o altering the db tables to accept something bigger than TEXT.
52 MAX_TRY_SIZE = 64000
commit-bot@chromium.org6d036c22014-04-09 18:59:44 +000053)
54
55var (
56 // codeTemplate is the cpp code template the user's code is copied into.
57 codeTemplate *template.Template = nil
58
commit-bot@chromium.org06aca012014-04-14 20:12:08 +000059 // indexTemplate is the main index.html page we serve.
60 indexTemplate *htemplate.Template = nil
61
commit-bot@chromium.org2dceeda2014-04-19 14:50:23 +000062 // iframeTemplate is the main index.html page we serve.
63 iframeTemplate *htemplate.Template = nil
64
commit-bot@chromium.orgd04e1dd2014-04-19 13:55:50 +000065 // recentTemplate is a list of recent images.
commit-bot@chromium.org06aca012014-04-14 20:12:08 +000066 recentTemplate *htemplate.Template = nil
commit-bot@chromium.org282333f2014-04-14 14:54:07 +000067
commit-bot@chromium.orgd04e1dd2014-04-19 13:55:50 +000068 // workspaceTemplate is the page for workspaces, a series of webtrys.
69 workspaceTemplate *htemplate.Template = nil
70
commit-bot@chromium.org282333f2014-04-14 14:54:07 +000071 // db is the database, nil if we don't have an SQL database to store data into.
72 db *sql.DB = nil
commit-bot@chromium.orgc81d1c42014-04-14 18:53:10 +000073
74 // directLink is the regex that matches URLs paths that are direct links.
commit-bot@chromium.org06aca012014-04-14 20:12:08 +000075 directLink = regexp.MustCompile("^/c/([a-f0-9]+)$")
76
commit-bot@chromium.org2dceeda2014-04-19 14:50:23 +000077 // iframeLink is the regex that matches URLs paths that are links to iframes.
78 iframeLink = regexp.MustCompile("^/iframe/([a-f0-9]+)$")
79
commit-bot@chromium.org06aca012014-04-14 20:12:08 +000080 // imageLink is the regex that matches URLs paths that are direct links to PNGs.
commit-bot@chromium.org0a7e5b72014-05-29 15:58:00 +000081 imageLink = regexp.MustCompile("^/i/([a-z0-9-]+.png)$")
commit-bot@chromium.orgd04e1dd2014-04-19 13:55:50 +000082
commit-bot@chromium.org35ffc442014-04-22 19:32:06 +000083 // tryInfoLink is the regex that matches URLs paths that are direct links to data about a single try.
84 tryInfoLink = regexp.MustCompile("^/json/([a-f0-9]+)$")
85
commit-bot@chromium.orgd04e1dd2014-04-19 13:55:50 +000086 // workspaceLink is the regex that matches URLs paths for workspaces.
87 workspaceLink = regexp.MustCompile("^/w/([a-z0-9-]+)$")
88
89 // workspaceNameAdj is a list of adjectives for building workspace names.
90 workspaceNameAdj = []string{
91 "autumn", "hidden", "bitter", "misty", "silent", "empty", "dry", "dark",
92 "summer", "icy", "delicate", "quiet", "white", "cool", "spring", "winter",
93 "patient", "twilight", "dawn", "crimson", "wispy", "weathered", "blue",
94 "billowing", "broken", "cold", "damp", "falling", "frosty", "green",
95 "long", "late", "lingering", "bold", "little", "morning", "muddy", "old",
96 "red", "rough", "still", "small", "sparkling", "throbbing", "shy",
97 "wandering", "withered", "wild", "black", "young", "holy", "solitary",
98 "fragrant", "aged", "snowy", "proud", "floral", "restless", "divine",
99 "polished", "ancient", "purple", "lively", "nameless",
100 }
101
102 // workspaceNameNoun is a list of nouns for building workspace names.
103 workspaceNameNoun = []string{
104 "waterfall", "river", "breeze", "moon", "rain", "wind", "sea", "morning",
105 "snow", "lake", "sunset", "pine", "shadow", "leaf", "dawn", "glitter",
106 "forest", "hill", "cloud", "meadow", "sun", "glade", "bird", "brook",
107 "butterfly", "bush", "dew", "dust", "field", "fire", "flower", "firefly",
108 "feather", "grass", "haze", "mountain", "night", "pond", "darkness",
109 "snowflake", "silence", "sound", "sky", "shape", "surf", "thunder",
110 "violet", "water", "wildflower", "wave", "water", "resonance", "sun",
111 "wood", "dream", "cherry", "tree", "fog", "frost", "voice", "paper",
112 "frog", "smoke", "star",
113 }
commit-bot@chromium.org472f8302014-04-28 15:33:31 +0000114
115 gitHash = ""
116 gitInfo = ""
jcgregorio65998812014-06-06 06:30:31 -0700117
118 requestsCounter = metrics.NewRegisteredCounter("requests", metrics.DefaultRegistry)
commit-bot@chromium.org6d036c22014-04-09 18:59:44 +0000119)
120
121// flags
122var (
123 useChroot = flag.Bool("use_chroot", false, "Run the compiled code in the schroot jail.")
commit-bot@chromium.org282333f2014-04-14 14:54:07 +0000124 port = flag.String("port", ":8000", "HTTP service address (e.g., ':8000')")
commit-bot@chromium.org6d036c22014-04-09 18:59:44 +0000125)
126
127// lineNumbers adds #line numbering to the user's code.
128func LineNumbers(c string) string {
129 lines := strings.Split(c, "\n")
130 ret := []string{}
131 for i, line := range lines {
132 ret = append(ret, fmt.Sprintf("#line %d", i+1))
133 ret = append(ret, line)
134 }
135 return strings.Join(ret, "\n")
136}
137
138func init() {
commit-bot@chromium.org2dceeda2014-04-19 14:50:23 +0000139 rand.Seed(time.Now().UnixNano())
commit-bot@chromium.orgc81d1c42014-04-14 18:53:10 +0000140
commit-bot@chromium.org6d036c22014-04-09 18:59:44 +0000141 // Change the current working directory to the directory of the executable.
142 var err error
143 cwd, err := filepath.Abs(filepath.Dir(os.Args[0]))
144 if err != nil {
145 log.Fatal(err)
146 }
147 os.Chdir(cwd)
148
149 codeTemplate, err = template.ParseFiles(filepath.Join(cwd, "templates/template.cpp"))
150 if err != nil {
151 panic(err)
152 }
commit-bot@chromium.orgd04e1dd2014-04-19 13:55:50 +0000153 indexTemplate, err = htemplate.ParseFiles(
154 filepath.Join(cwd, "templates/index.html"),
155 filepath.Join(cwd, "templates/titlebar.html"),
commit-bot@chromium.org90041922014-04-22 21:13:45 +0000156 filepath.Join(cwd, "templates/content.html"),
commit-bot@chromium.org015c08e2014-05-19 16:40:08 +0000157 filepath.Join(cwd, "templates/headercommon.html"),
158 filepath.Join(cwd, "templates/footercommon.html"),
commit-bot@chromium.orgd04e1dd2014-04-19 13:55:50 +0000159 )
commit-bot@chromium.org06aca012014-04-14 20:12:08 +0000160 if err != nil {
161 panic(err)
162 }
commit-bot@chromium.org2dceeda2014-04-19 14:50:23 +0000163 iframeTemplate, err = htemplate.ParseFiles(
164 filepath.Join(cwd, "templates/iframe.html"),
commit-bot@chromium.org90041922014-04-22 21:13:45 +0000165 filepath.Join(cwd, "templates/content.html"),
commit-bot@chromium.org015c08e2014-05-19 16:40:08 +0000166 filepath.Join(cwd, "templates/headercommon.html"),
167 filepath.Join(cwd, "templates/footercommon.html"),
commit-bot@chromium.org2dceeda2014-04-19 14:50:23 +0000168 )
169 if err != nil {
170 panic(err)
171 }
commit-bot@chromium.orgd04e1dd2014-04-19 13:55:50 +0000172 recentTemplate, err = htemplate.ParseFiles(
173 filepath.Join(cwd, "templates/recent.html"),
174 filepath.Join(cwd, "templates/titlebar.html"),
commit-bot@chromium.org015c08e2014-05-19 16:40:08 +0000175 filepath.Join(cwd, "templates/headercommon.html"),
176 filepath.Join(cwd, "templates/footercommon.html"),
commit-bot@chromium.orgd04e1dd2014-04-19 13:55:50 +0000177 )
178 if err != nil {
179 panic(err)
180 }
181 workspaceTemplate, err = htemplate.ParseFiles(
182 filepath.Join(cwd, "templates/workspace.html"),
183 filepath.Join(cwd, "templates/titlebar.html"),
commit-bot@chromium.org90041922014-04-22 21:13:45 +0000184 filepath.Join(cwd, "templates/content.html"),
commit-bot@chromium.org015c08e2014-05-19 16:40:08 +0000185 filepath.Join(cwd, "templates/headercommon.html"),
186 filepath.Join(cwd, "templates/footercommon.html"),
commit-bot@chromium.orgd04e1dd2014-04-19 13:55:50 +0000187 )
commit-bot@chromium.org6d036c22014-04-09 18:59:44 +0000188 if err != nil {
189 panic(err)
190 }
commit-bot@chromium.org282333f2014-04-14 14:54:07 +0000191
commit-bot@chromium.org472f8302014-04-28 15:33:31 +0000192 // The git command returns output of the format:
193 //
194 // f672cead70404080a991ebfb86c38316a4589b23 2014-04-27 19:21:51 +0000
195 //
196 logOutput, err := doCmd(`git log --format=%H%x20%ai HEAD^..HEAD`, true)
197 if err != nil {
198 panic(err)
199 }
200 logInfo := strings.Split(logOutput, " ")
201 gitHash = logInfo[0]
202 gitInfo = logInfo[1] + " " + logInfo[2] + " " + logInfo[0][0:6]
203
commit-bot@chromium.org282333f2014-04-14 14:54:07 +0000204 // Connect to MySQL server. First, get the password from the metadata server.
205 // See https://developers.google.com/compute/docs/metadata#custom.
206 req, err := http.NewRequest("GET", "http://metadata/computeMetadata/v1/instance/attributes/password", nil)
207 if err != nil {
208 panic(err)
209 }
210 client := http.Client{}
211 req.Header.Add("X-Google-Metadata-Request", "True")
212 if resp, err := client.Do(req); err == nil {
213 password, err := ioutil.ReadAll(resp.Body)
214 if err != nil {
215 log.Printf("ERROR: Failed to read password from metadata server: %q\n", err)
216 panic(err)
217 }
218 // The IP address of the database is found here:
219 // https://console.developers.google.com/project/31977622648/sql/instances/webtry/overview
220 // And 3306 is the default port for MySQL.
commit-bot@chromium.org4bd8fdc2014-04-15 00:43:51 +0000221 db, err = sql.Open("mysql", fmt.Sprintf("webtry:%s@tcp(173.194.83.52:3306)/webtry?parseTime=true", password))
commit-bot@chromium.org282333f2014-04-14 14:54:07 +0000222 if err != nil {
223 log.Printf("ERROR: Failed to open connection to SQL server: %q\n", err)
224 panic(err)
225 }
226 } else {
commit-bot@chromium.orgd04e1dd2014-04-19 13:55:50 +0000227 log.Printf("INFO: Failed to find metadata, unable to connect to MySQL server (Expected when running locally): %q\n", err)
commit-bot@chromium.orgc81d1c42014-04-14 18:53:10 +0000228 // Fallback to sqlite for local use.
229 db, err = sql.Open("sqlite3", "./webtry.db")
230 if err != nil {
231 log.Printf("ERROR: Failed to open: %q\n", err)
232 panic(err)
233 }
commit-bot@chromium.org0a7e5b72014-05-29 15:58:00 +0000234 sql := `CREATE TABLE source_images (
235 id INTEGER PRIMARY KEY NOT NULL,
236 image MEDIUMBLOB DEFAULT '' NOT NULL, -- formatted as a PNG.
237 width INTEGER DEFAULT 0 NOT NULL,
238 height INTEGER DEFAULT 0 NOT NULL,
239 create_ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
240 hidden INTEGER DEFAULT 0 NOT NULL
241 )`
242 _, err = db.Exec(sql)
243 log.Printf("Info: status creating sqlite table for sources: %q\n", err)
244
245 sql = `CREATE TABLE webtry (
246 code TEXT DEFAULT '' NOT NULL,
247 create_ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
248 hash CHAR(64) DEFAULT '' NOT NULL,
249 source_image_id INTEGER DEFAULT 0 NOT NULL,
250
commit-bot@chromium.orgc81d1c42014-04-14 18:53:10 +0000251 PRIMARY KEY(hash)
252 )`
commit-bot@chromium.orgd04e1dd2014-04-19 13:55:50 +0000253 _, err = db.Exec(sql)
254 log.Printf("Info: status creating sqlite table for webtry: %q\n", err)
commit-bot@chromium.org0a7e5b72014-05-29 15:58:00 +0000255
commit-bot@chromium.orgd04e1dd2014-04-19 13:55:50 +0000256 sql = `CREATE TABLE workspace (
257 name CHAR(64) DEFAULT '' NOT NULL,
258 create_ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
259 PRIMARY KEY(name)
260 )`
261 _, err = db.Exec(sql)
262 log.Printf("Info: status creating sqlite table for workspace: %q\n", err)
commit-bot@chromium.orgd04e1dd2014-04-19 13:55:50 +0000263
commit-bot@chromium.org0a7e5b72014-05-29 15:58:00 +0000264 sql = `CREATE TABLE workspacetry (
265 name CHAR(64) DEFAULT '' NOT NULL,
266 create_ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
267 hash CHAR(64) DEFAULT '' NOT NULL,
268 hidden INTEGER DEFAULT 0 NOT NULL,
269 source_image_id INTEGER DEFAULT 0 NOT NULL,
270
271 FOREIGN KEY (name) REFERENCES workspace(name)
commit-bot@chromium.orgd04e1dd2014-04-19 13:55:50 +0000272 )`
273 _, err = db.Exec(sql)
274 log.Printf("Info: status creating sqlite table for workspace try: %q\n", err)
commit-bot@chromium.org282333f2014-04-14 14:54:07 +0000275 }
commit-bot@chromium.org9f3b9252014-05-14 12:55:34 +0000276
277 // Ping the database to keep the connection fresh.
278 go func() {
279 c := time.Tick(1 * time.Minute)
280 for _ = range c {
281 if err := db.Ping(); err != nil {
282 log.Printf("ERROR: Database failed to respond: %q\n", err)
283 }
284 }
285 }()
286
jcgregorio65998812014-06-06 06:30:31 -0700287 metrics.RegisterRuntimeMemStats(metrics.DefaultRegistry)
288 go metrics.CaptureRuntimeMemStats(metrics.DefaultRegistry, 1*time.Minute)
289
290 // Start reporting metrics.
291 // TODO(jcgregorio) We need a centrialized config server for storing things
292 // like the IP address of the Graphite monitor.
jcgregoriod0eecd42014-06-18 07:13:09 -0700293 addr, _ := net.ResolveTCPAddr("tcp", "skia-monitoring-b:2003")
jcgregorio65998812014-06-06 06:30:31 -0700294 go metrics.Graphite(metrics.DefaultRegistry, 1*time.Minute, "webtry", addr)
295
commit-bot@chromium.org0a7e5b72014-05-29 15:58:00 +0000296 writeOutAllSourceImages()
297}
298
299func writeOutAllSourceImages() {
300 // Pull all the source images from the db and write them out to inout.
301 rows, err := db.Query("SELECT id, image, create_ts FROM source_images ORDER BY create_ts DESC")
302
303 if err != nil {
304 log.Printf("ERROR: Failed to open connection to SQL server: %q\n", err)
305 panic(err)
306 }
307 for rows.Next() {
308 var id int
309 var image []byte
310 var create_ts time.Time
311 if err := rows.Scan(&id, &image, &create_ts); err != nil {
312 log.Printf("Error: failed to fetch from database: %q", err)
313 continue
314 }
315 filename := fmt.Sprintf("../../../inout/image-%d.png", id)
316 if _, err := os.Stat(filename); os.IsExist(err) {
317 log.Printf("Skipping write since file exists: %q", filename)
318 continue
319 }
320 if err := ioutil.WriteFile(filename, image, 0666); err != nil {
321 log.Printf("Error: failed to write image file: %q", err)
322 }
323 }
commit-bot@chromium.org6d036c22014-04-09 18:59:44 +0000324}
325
commit-bot@chromium.org472f8302014-04-28 15:33:31 +0000326// Titlebar is used in titlebar template expansion.
327type Titlebar struct {
328 GitHash string
329 GitInfo string
330}
331
commit-bot@chromium.org6d036c22014-04-09 18:59:44 +0000332// userCode is used in template expansion.
333type userCode struct {
commit-bot@chromium.org472f8302014-04-28 15:33:31 +0000334 Code string
335 Hash string
commit-bot@chromium.org0a7e5b72014-05-29 15:58:00 +0000336 Source int
commit-bot@chromium.org472f8302014-04-28 15:33:31 +0000337 Titlebar Titlebar
commit-bot@chromium.org6d036c22014-04-09 18:59:44 +0000338}
339
340// expandToFile expands the template and writes the result to the file.
341func expandToFile(filename string, code string, t *template.Template) error {
342 f, err := os.Create(filename)
343 if err != nil {
344 return err
345 }
346 defer f.Close()
commit-bot@chromium.org472f8302014-04-28 15:33:31 +0000347 return t.Execute(f, userCode{Code: code, Titlebar: Titlebar{GitHash: gitHash, GitInfo: gitInfo}})
commit-bot@chromium.org6d036c22014-04-09 18:59:44 +0000348}
349
commit-bot@chromium.org0a7e5b72014-05-29 15:58:00 +0000350// expandCode expands the template into a file and calculates the MD5 hash.
351func expandCode(code string, source int) (string, error) {
commit-bot@chromium.org6d036c22014-04-09 18:59:44 +0000352 h := md5.New()
353 h.Write([]byte(code))
commit-bot@chromium.org0a7e5b72014-05-29 15:58:00 +0000354 binary.Write(h, binary.LittleEndian, int64(source))
commit-bot@chromium.org6d036c22014-04-09 18:59:44 +0000355 hash := fmt.Sprintf("%x", h.Sum(nil))
356 // At this point we are running in skia/experimental/webtry, making cache a
357 // peer directory to skia.
358 // TODO(jcgregorio) Make all relative directories into flags.
359 err := expandToFile(fmt.Sprintf("../../../cache/%s.cpp", hash), code, codeTemplate)
360 return hash, err
361}
362
363// response is serialized to JSON as a response to POSTs.
364type response struct {
365 Message string `json:"message"`
commit-bot@chromium.org90041922014-04-22 21:13:45 +0000366 StdOut string `json:"stdout"`
commit-bot@chromium.org6d036c22014-04-09 18:59:44 +0000367 Img string `json:"img"`
commit-bot@chromium.orgc81d1c42014-04-14 18:53:10 +0000368 Hash string `json:"hash"`
commit-bot@chromium.org6d036c22014-04-09 18:59:44 +0000369}
370
371// doCmd executes the given command line string in either the out/Debug
commit-bot@chromium.org15b29812014-04-28 14:56:32 +0000372// directory or the inout directory. Returns the stdout and stderr.
commit-bot@chromium.org6d036c22014-04-09 18:59:44 +0000373func doCmd(commandLine string, moveToDebug bool) (string, error) {
374 log.Printf("Command: %q\n", commandLine)
375 programAndArgs := strings.SplitN(commandLine, " ", 2)
376 program := programAndArgs[0]
377 args := []string{}
378 if len(programAndArgs) > 1 {
379 args = strings.Split(programAndArgs[1], " ")
380 }
381 cmd := exec.Command(program, args...)
382 abs, err := filepath.Abs("../../out/Debug")
383 if err != nil {
384 return "", fmt.Errorf("Failed to find absolute path to Debug directory.")
385 }
386 if moveToDebug {
387 cmd.Dir = abs
388 } else if !*useChroot { // Don't set cmd.Dir when using chroot.
389 abs, err := filepath.Abs("../../../inout")
390 if err != nil {
391 return "", fmt.Errorf("Failed to find absolute path to inout directory.")
392 }
393 cmd.Dir = abs
394 }
395 log.Printf("Run in directory: %q\n", cmd.Dir)
commit-bot@chromium.org15b29812014-04-28 14:56:32 +0000396 message, err := cmd.CombinedOutput()
397 log.Printf("StdOut + StdErr: %s\n", string(message))
commit-bot@chromium.org6d036c22014-04-09 18:59:44 +0000398 if err != nil {
399 log.Printf("Exit status: %s\n", err.Error())
commit-bot@chromium.org15b29812014-04-28 14:56:32 +0000400 return string(message), fmt.Errorf("Failed to run command.")
commit-bot@chromium.org6d036c22014-04-09 18:59:44 +0000401 }
commit-bot@chromium.org15b29812014-04-28 14:56:32 +0000402 return string(message), nil
commit-bot@chromium.org6d036c22014-04-09 18:59:44 +0000403}
404
405// reportError formats an HTTP error response and also logs the detailed error message.
406func reportError(w http.ResponseWriter, r *http.Request, err error, message string) {
commit-bot@chromium.org90041922014-04-22 21:13:45 +0000407 log.Printf("Error: %s\n%s", message, err.Error())
commit-bot@chromium.orga4c36772014-05-19 17:38:52 +0000408 w.Header().Set("Content-Type", "text/plain")
commit-bot@chromium.org90041922014-04-22 21:13:45 +0000409 http.Error(w, message, 500)
410}
411
412// reportTryError formats an HTTP error response in JSON and also logs the detailed error message.
413func reportTryError(w http.ResponseWriter, r *http.Request, err error, message, hash string) {
commit-bot@chromium.org6d036c22014-04-09 18:59:44 +0000414 m := response{
415 Message: message,
commit-bot@chromium.org90041922014-04-22 21:13:45 +0000416 Hash: hash,
commit-bot@chromium.org6d036c22014-04-09 18:59:44 +0000417 }
418 log.Printf("Error: %s\n%s", message, err.Error())
419 resp, err := json.Marshal(m)
420 if err != nil {
421 http.Error(w, "Failed to serialize a response", 500)
422 return
423 }
commit-bot@chromium.orga4c36772014-05-19 17:38:52 +0000424 w.Header().Set("Content-Type", "text/plain")
commit-bot@chromium.org6d036c22014-04-09 18:59:44 +0000425 w.Write(resp)
426}
427
commit-bot@chromium.org0a7e5b72014-05-29 15:58:00 +0000428func writeToDatabase(hash string, code string, workspaceName string, source int) {
commit-bot@chromium.org282333f2014-04-14 14:54:07 +0000429 if db == nil {
430 return
431 }
commit-bot@chromium.org0a7e5b72014-05-29 15:58:00 +0000432 if _, err := db.Exec("INSERT INTO webtry (code, hash, source_image_id) VALUES(?, ?, ?)", code, hash, source); err != nil {
commit-bot@chromium.org282333f2014-04-14 14:54:07 +0000433 log.Printf("ERROR: Failed to insert code into database: %q\n", err)
434 }
commit-bot@chromium.orgd04e1dd2014-04-19 13:55:50 +0000435 if workspaceName != "" {
commit-bot@chromium.org0a7e5b72014-05-29 15:58:00 +0000436 if _, err := db.Exec("INSERT INTO workspacetry (name, hash, source_image_id) VALUES(?, ?, ?)", workspaceName, hash, source); err != nil {
commit-bot@chromium.orgd04e1dd2014-04-19 13:55:50 +0000437 log.Printf("ERROR: Failed to insert into workspacetry table: %q\n", err)
438 }
439 }
commit-bot@chromium.org282333f2014-04-14 14:54:07 +0000440}
441
commit-bot@chromium.org0a7e5b72014-05-29 15:58:00 +0000442type Sources struct {
443 Id int `json:"id"`
444}
445
446// sourcesHandler serves up the PNG of a specific try.
447func sourcesHandler(w http.ResponseWriter, r *http.Request) {
448 log.Printf("Sources Handler: %q\n", r.URL.Path)
449 if r.Method == "GET" {
450 rows, err := db.Query("SELECT id, create_ts FROM source_images WHERE hidden=0 ORDER BY create_ts DESC")
451
452 if err != nil {
453 http.Error(w, fmt.Sprintf("Failed to query sources: %s.", err), 500)
454 }
455 sources := make([]Sources, 0, 0)
456 for rows.Next() {
457 var id int
458 var create_ts time.Time
459 if err := rows.Scan(&id, &create_ts); err != nil {
460 log.Printf("Error: failed to fetch from database: %q", err)
461 continue
462 }
463 sources = append(sources, Sources{Id: id})
464 }
465
466 resp, err := json.Marshal(sources)
467 if err != nil {
468 reportError(w, r, err, "Failed to serialize a response.")
469 return
470 }
471 w.Header().Set("Content-Type", "application/json")
472 w.Write(resp)
473
474 } else if r.Method == "POST" {
475 if err := r.ParseMultipartForm(1000000); err != nil {
476 http.Error(w, fmt.Sprintf("Failed to load image: %s.", err), 500)
477 return
478 }
479 if _, ok := r.MultipartForm.File["upload"]; !ok {
480 http.Error(w, "Invalid upload.", 500)
481 return
482 }
483 if len(r.MultipartForm.File["upload"]) != 1 {
484 http.Error(w, "Wrong number of uploads.", 500)
485 return
486 }
487 f, err := r.MultipartForm.File["upload"][0].Open()
488 if err != nil {
489 http.Error(w, fmt.Sprintf("Failed to load image: %s.", err), 500)
490 return
491 }
492 defer f.Close()
493 m, _, err := image.Decode(f)
494 if err != nil {
495 http.Error(w, fmt.Sprintf("Failed to decode image: %s.", err), 500)
496 return
497 }
498 var b bytes.Buffer
499 png.Encode(&b, m)
500 bounds := m.Bounds()
501 width := bounds.Max.Y - bounds.Min.Y
502 height := bounds.Max.X - bounds.Min.X
503 if _, err := db.Exec("INSERT INTO source_images (image, width, height) VALUES(?, ?, ?)", b.Bytes(), width, height); err != nil {
504 log.Printf("ERROR: Failed to insert sources into database: %q\n", err)
505 http.Error(w, fmt.Sprintf("Failed to store image: %s.", err), 500)
506 return
507 }
508 go writeOutAllSourceImages()
509
510 // Now redirect back to where we came from.
511 http.Redirect(w, r, r.Referer(), 302)
512 } else {
513 http.NotFound(w, r)
514 return
515 }
516}
517
commit-bot@chromium.org06aca012014-04-14 20:12:08 +0000518// imageHandler serves up the PNG of a specific try.
519func imageHandler(w http.ResponseWriter, r *http.Request) {
520 log.Printf("Image Handler: %q\n", r.URL.Path)
521 if r.Method != "GET" {
522 http.NotFound(w, r)
523 return
524 }
525 match := imageLink.FindStringSubmatch(r.URL.Path)
526 if len(match) != 2 {
527 http.NotFound(w, r)
528 return
529 }
530 filename := match[1]
commit-bot@chromium.orga4c36772014-05-19 17:38:52 +0000531 w.Header().Set("Content-Type", "image/png")
commit-bot@chromium.org06aca012014-04-14 20:12:08 +0000532 http.ServeFile(w, r, fmt.Sprintf("../../../inout/%s", filename))
533}
534
535type Try struct {
commit-bot@chromium.orgc3b738a2014-04-21 17:36:44 +0000536 Hash string `json:"hash"`
commit-bot@chromium.org0a7e5b72014-05-29 15:58:00 +0000537 Source int
commit-bot@chromium.orgc3b738a2014-04-21 17:36:44 +0000538 CreateTS string `json:"create_ts"`
commit-bot@chromium.org06aca012014-04-14 20:12:08 +0000539}
540
541type Recent struct {
commit-bot@chromium.org472f8302014-04-28 15:33:31 +0000542 Tries []Try
543 Titlebar Titlebar
commit-bot@chromium.org06aca012014-04-14 20:12:08 +0000544}
545
546// recentHandler shows the last 20 tries.
547func recentHandler(w http.ResponseWriter, r *http.Request) {
548 log.Printf("Recent Handler: %q\n", r.URL.Path)
549
550 var err error
551 rows, err := db.Query("SELECT create_ts, hash FROM webtry ORDER BY create_ts DESC LIMIT 20")
552 if err != nil {
553 http.NotFound(w, r)
554 return
555 }
556 recent := []Try{}
557 for rows.Next() {
558 var hash string
559 var create_ts time.Time
560 if err := rows.Scan(&create_ts, &hash); err != nil {
561 log.Printf("Error: failed to fetch from database: %q", err)
562 continue
563 }
564 recent = append(recent, Try{Hash: hash, CreateTS: create_ts.Format("2006-02-01")})
565 }
commit-bot@chromium.orga4c36772014-05-19 17:38:52 +0000566 w.Header().Set("Content-Type", "text/html")
commit-bot@chromium.org472f8302014-04-28 15:33:31 +0000567 if err := recentTemplate.Execute(w, Recent{Tries: recent, Titlebar: Titlebar{GitHash: gitHash, GitInfo: gitInfo}}); err != nil {
commit-bot@chromium.org06aca012014-04-14 20:12:08 +0000568 log.Printf("ERROR: Failed to expand template: %q\n", err)
569 }
570}
571
commit-bot@chromium.orgd04e1dd2014-04-19 13:55:50 +0000572type Workspace struct {
commit-bot@chromium.org472f8302014-04-28 15:33:31 +0000573 Name string
574 Code string
575 Hash string
commit-bot@chromium.org0a7e5b72014-05-29 15:58:00 +0000576 Source int
commit-bot@chromium.org472f8302014-04-28 15:33:31 +0000577 Tries []Try
578 Titlebar Titlebar
commit-bot@chromium.orgd04e1dd2014-04-19 13:55:50 +0000579}
580
581// newWorkspace generates a new random workspace name and stores it in the database.
582func newWorkspace() (string, error) {
583 for i := 0; i < 10; i++ {
584 adj := workspaceNameAdj[rand.Intn(len(workspaceNameAdj))]
585 noun := workspaceNameNoun[rand.Intn(len(workspaceNameNoun))]
586 suffix := rand.Intn(1000)
587 name := fmt.Sprintf("%s-%s-%d", adj, noun, suffix)
588 if _, err := db.Exec("INSERT INTO workspace (name) VALUES(?)", name); err == nil {
589 return name, nil
590 } else {
591 log.Printf("ERROR: Failed to insert workspace into database: %q\n", err)
592 }
593 }
594 return "", fmt.Errorf("Failed to create a new workspace")
595}
596
597// getCode returns the code for a given hash, or the empty string if not found.
commit-bot@chromium.org0a7e5b72014-05-29 15:58:00 +0000598func getCode(hash string) (string, int, error) {
commit-bot@chromium.orgd04e1dd2014-04-19 13:55:50 +0000599 code := ""
commit-bot@chromium.org0a7e5b72014-05-29 15:58:00 +0000600 source := 0
601 if err := db.QueryRow("SELECT code, source_image_id FROM webtry WHERE hash=?", hash).Scan(&code, &source); err != nil {
commit-bot@chromium.orgd04e1dd2014-04-19 13:55:50 +0000602 log.Printf("ERROR: Code for hash is missing: %q\n", err)
commit-bot@chromium.org0a7e5b72014-05-29 15:58:00 +0000603 return code, source, err
commit-bot@chromium.orgd04e1dd2014-04-19 13:55:50 +0000604 }
commit-bot@chromium.org0a7e5b72014-05-29 15:58:00 +0000605 return code, source, nil
commit-bot@chromium.orgd04e1dd2014-04-19 13:55:50 +0000606}
607
608func workspaceHandler(w http.ResponseWriter, r *http.Request) {
609 log.Printf("Workspace Handler: %q\n", r.URL.Path)
610 if r.Method == "GET" {
611 tries := []Try{}
612 match := workspaceLink.FindStringSubmatch(r.URL.Path)
613 name := ""
614 if len(match) == 2 {
615 name = match[1]
commit-bot@chromium.org0a7e5b72014-05-29 15:58:00 +0000616 rows, err := db.Query("SELECT create_ts, hash, source_image_id FROM workspacetry WHERE name=? ORDER BY create_ts", name)
commit-bot@chromium.orgd04e1dd2014-04-19 13:55:50 +0000617 if err != nil {
618 reportError(w, r, err, "Failed to select.")
619 return
620 }
621 for rows.Next() {
622 var hash string
623 var create_ts time.Time
commit-bot@chromium.org0a7e5b72014-05-29 15:58:00 +0000624 var source int
625 if err := rows.Scan(&create_ts, &hash, &source); err != nil {
commit-bot@chromium.orgd04e1dd2014-04-19 13:55:50 +0000626 log.Printf("Error: failed to fetch from database: %q", err)
627 continue
628 }
commit-bot@chromium.org0a7e5b72014-05-29 15:58:00 +0000629 tries = append(tries, Try{Hash: hash, Source: source, CreateTS: create_ts.Format("2006-02-01")})
commit-bot@chromium.orgd04e1dd2014-04-19 13:55:50 +0000630 }
631 }
632 var code string
commit-bot@chromium.org35ffc442014-04-22 19:32:06 +0000633 var hash string
commit-bot@chromium.org0a7e5b72014-05-29 15:58:00 +0000634 source := 0
commit-bot@chromium.orgd04e1dd2014-04-19 13:55:50 +0000635 if len(tries) == 0 {
636 code = DEFAULT_SAMPLE
637 } else {
commit-bot@chromium.org35ffc442014-04-22 19:32:06 +0000638 hash = tries[len(tries)-1].Hash
commit-bot@chromium.org0a7e5b72014-05-29 15:58:00 +0000639 code, source, _ = getCode(hash)
commit-bot@chromium.orgd04e1dd2014-04-19 13:55:50 +0000640 }
commit-bot@chromium.orga4c36772014-05-19 17:38:52 +0000641 w.Header().Set("Content-Type", "text/html")
commit-bot@chromium.org0a7e5b72014-05-29 15:58:00 +0000642 if err := workspaceTemplate.Execute(w, Workspace{Tries: tries, Code: code, Name: name, Hash: hash, Source: source, Titlebar: Titlebar{GitHash: gitHash, GitInfo: gitInfo}}); err != nil {
commit-bot@chromium.orgd04e1dd2014-04-19 13:55:50 +0000643 log.Printf("ERROR: Failed to expand template: %q\n", err)
644 }
645 } else if r.Method == "POST" {
646 name, err := newWorkspace()
647 if err != nil {
648 http.Error(w, "Failed to create a new workspace.", 500)
649 return
650 }
651 http.Redirect(w, r, "/w/"+name, 302)
652 }
653}
654
commit-bot@chromium.org4bd8fdc2014-04-15 00:43:51 +0000655// hasPreProcessor returns true if any line in the code begins with a # char.
656func hasPreProcessor(code string) bool {
657 lines := strings.Split(code, "\n")
658 for _, s := range lines {
659 if strings.HasPrefix(strings.TrimSpace(s), "#") {
660 return true
661 }
662 }
663 return false
664}
665
commit-bot@chromium.orgd04e1dd2014-04-19 13:55:50 +0000666type TryRequest struct {
commit-bot@chromium.org0a7e5b72014-05-29 15:58:00 +0000667 Code string `json:"code"`
668 Name string `json:"name"` // Optional name of the workspace the code is in.
669 Source int `json:"source"` // ID of the source image, 0 if none.
commit-bot@chromium.orgd04e1dd2014-04-19 13:55:50 +0000670}
671
commit-bot@chromium.org2dceeda2014-04-19 14:50:23 +0000672// iframeHandler handles the GET and POST of the main page.
673func iframeHandler(w http.ResponseWriter, r *http.Request) {
674 log.Printf("IFrame Handler: %q\n", r.URL.Path)
675 if r.Method != "GET" {
676 http.NotFound(w, r)
677 return
678 }
679 match := iframeLink.FindStringSubmatch(r.URL.Path)
680 if len(match) != 2 {
681 http.NotFound(w, r)
682 return
683 }
684 hash := match[1]
685 if db == nil {
686 http.NotFound(w, r)
687 return
688 }
689 var code string
commit-bot@chromium.org0a7e5b72014-05-29 15:58:00 +0000690 code, source, err := getCode(hash)
commit-bot@chromium.org35ffc442014-04-22 19:32:06 +0000691 if err != nil {
commit-bot@chromium.org2dceeda2014-04-19 14:50:23 +0000692 http.NotFound(w, r)
693 return
694 }
695 // Expand the template.
commit-bot@chromium.orga4c36772014-05-19 17:38:52 +0000696 w.Header().Set("Content-Type", "text/html")
commit-bot@chromium.org0a7e5b72014-05-29 15:58:00 +0000697 if err := iframeTemplate.Execute(w, userCode{Code: code, Hash: hash, Source: source}); err != nil {
commit-bot@chromium.org2dceeda2014-04-19 14:50:23 +0000698 log.Printf("ERROR: Failed to expand template: %q\n", err)
699 }
700}
701
commit-bot@chromium.org35ffc442014-04-22 19:32:06 +0000702type TryInfo struct {
commit-bot@chromium.org0a7e5b72014-05-29 15:58:00 +0000703 Hash string `json:"hash"`
704 Code string `json:"code"`
705 Source int `json:"source"`
commit-bot@chromium.org35ffc442014-04-22 19:32:06 +0000706}
707
708// tryInfoHandler returns information about a specific try.
709func tryInfoHandler(w http.ResponseWriter, r *http.Request) {
710 log.Printf("Try Info Handler: %q\n", r.URL.Path)
711 if r.Method != "GET" {
712 http.NotFound(w, r)
713 return
714 }
715 match := tryInfoLink.FindStringSubmatch(r.URL.Path)
716 if len(match) != 2 {
717 http.NotFound(w, r)
718 return
719 }
720 hash := match[1]
commit-bot@chromium.org0a7e5b72014-05-29 15:58:00 +0000721 code, source, err := getCode(hash)
commit-bot@chromium.org35ffc442014-04-22 19:32:06 +0000722 if err != nil {
723 http.NotFound(w, r)
724 return
725 }
726 m := TryInfo{
commit-bot@chromium.org0a7e5b72014-05-29 15:58:00 +0000727 Hash: hash,
728 Code: code,
729 Source: source,
commit-bot@chromium.org35ffc442014-04-22 19:32:06 +0000730 }
731 resp, err := json.Marshal(m)
732 if err != nil {
733 reportError(w, r, err, "Failed to serialize a response.")
734 return
735 }
736 w.Header().Set("Content-Type", "application/json")
737 w.Write(resp)
738}
739
commit-bot@chromium.org90041922014-04-22 21:13:45 +0000740func cleanCompileOutput(s, hash string) string {
741 old := "../../../cache/" + hash + ".cpp:"
742 log.Printf("INFO: replacing %q\n", old)
743 return strings.Replace(s, old, "usercode.cpp:", -1)
744}
745
commit-bot@chromium.org6d036c22014-04-09 18:59:44 +0000746// mainHandler handles the GET and POST of the main page.
747func mainHandler(w http.ResponseWriter, r *http.Request) {
commit-bot@chromium.org06aca012014-04-14 20:12:08 +0000748 log.Printf("Main Handler: %q\n", r.URL.Path)
jcgregorio65998812014-06-06 06:30:31 -0700749 requestsCounter.Inc(1)
commit-bot@chromium.org6d036c22014-04-09 18:59:44 +0000750 if r.Method == "GET" {
commit-bot@chromium.orgc81d1c42014-04-14 18:53:10 +0000751 code := DEFAULT_SAMPLE
commit-bot@chromium.org0a7e5b72014-05-29 15:58:00 +0000752 source := 0
commit-bot@chromium.orgc81d1c42014-04-14 18:53:10 +0000753 match := directLink.FindStringSubmatch(r.URL.Path)
commit-bot@chromium.org35ffc442014-04-22 19:32:06 +0000754 var hash string
commit-bot@chromium.org06aca012014-04-14 20:12:08 +0000755 if len(match) == 2 && r.URL.Path != "/" {
commit-bot@chromium.org35ffc442014-04-22 19:32:06 +0000756 hash = match[1]
commit-bot@chromium.orgc81d1c42014-04-14 18:53:10 +0000757 if db == nil {
758 http.NotFound(w, r)
759 return
760 }
761 // Update 'code' with the code found in the database.
commit-bot@chromium.org0a7e5b72014-05-29 15:58:00 +0000762 if err := db.QueryRow("SELECT code, source_image_id FROM webtry WHERE hash=?", hash).Scan(&code, &source); err != nil {
commit-bot@chromium.orgc81d1c42014-04-14 18:53:10 +0000763 http.NotFound(w, r)
764 return
765 }
766 }
767 // Expand the template.
commit-bot@chromium.orga4c36772014-05-19 17:38:52 +0000768 w.Header().Set("Content-Type", "text/html")
commit-bot@chromium.org0a7e5b72014-05-29 15:58:00 +0000769 if err := indexTemplate.Execute(w, userCode{Code: code, Hash: hash, Source: source, Titlebar: Titlebar{GitHash: gitHash, GitInfo: gitInfo}}); err != nil {
commit-bot@chromium.orgc81d1c42014-04-14 18:53:10 +0000770 log.Printf("ERROR: Failed to expand template: %q\n", err)
771 }
commit-bot@chromium.org6d036c22014-04-09 18:59:44 +0000772 } else if r.Method == "POST" {
773 w.Header().Set("Content-Type", "application/json")
commit-bot@chromium.org4bd8fdc2014-04-15 00:43:51 +0000774 buf := bytes.NewBuffer(make([]byte, 0, MAX_TRY_SIZE))
775 n, err := buf.ReadFrom(r.Body)
commit-bot@chromium.org6d036c22014-04-09 18:59:44 +0000776 if err != nil {
commit-bot@chromium.org90041922014-04-22 21:13:45 +0000777 reportTryError(w, r, err, "Failed to read a request body.", "")
commit-bot@chromium.org6d036c22014-04-09 18:59:44 +0000778 return
779 }
commit-bot@chromium.org4bd8fdc2014-04-15 00:43:51 +0000780 if n == MAX_TRY_SIZE {
781 err := fmt.Errorf("Code length equal to, or exceeded, %d", MAX_TRY_SIZE)
commit-bot@chromium.org90041922014-04-22 21:13:45 +0000782 reportTryError(w, r, err, "Code too large.", "")
commit-bot@chromium.org4bd8fdc2014-04-15 00:43:51 +0000783 return
784 }
commit-bot@chromium.orgd04e1dd2014-04-19 13:55:50 +0000785 request := TryRequest{}
786 if err := json.Unmarshal(buf.Bytes(), &request); err != nil {
commit-bot@chromium.org90041922014-04-22 21:13:45 +0000787 reportTryError(w, r, err, "Coulnd't decode JSON.", "")
commit-bot@chromium.orgd04e1dd2014-04-19 13:55:50 +0000788 return
789 }
790 if hasPreProcessor(request.Code) {
commit-bot@chromium.org4bd8fdc2014-04-15 00:43:51 +0000791 err := fmt.Errorf("Found preprocessor macro in code.")
commit-bot@chromium.org90041922014-04-22 21:13:45 +0000792 reportTryError(w, r, err, "Preprocessor macros aren't allowed.", "")
commit-bot@chromium.org4bd8fdc2014-04-15 00:43:51 +0000793 return
794 }
commit-bot@chromium.org0a7e5b72014-05-29 15:58:00 +0000795 hash, err := expandCode(LineNumbers(request.Code), request.Source)
commit-bot@chromium.org6d036c22014-04-09 18:59:44 +0000796 if err != nil {
commit-bot@chromium.org90041922014-04-22 21:13:45 +0000797 reportTryError(w, r, err, "Failed to write the code to compile.", hash)
commit-bot@chromium.org6d036c22014-04-09 18:59:44 +0000798 return
799 }
commit-bot@chromium.org0a7e5b72014-05-29 15:58:00 +0000800 writeToDatabase(hash, request.Code, request.Name, request.Source)
commit-bot@chromium.org6d036c22014-04-09 18:59:44 +0000801 message, err := doCmd(fmt.Sprintf(RESULT_COMPILE, hash, hash), true)
802 if err != nil {
commit-bot@chromium.org90041922014-04-22 21:13:45 +0000803 message = cleanCompileOutput(message, hash)
804 reportTryError(w, r, err, message, hash)
commit-bot@chromium.org6d036c22014-04-09 18:59:44 +0000805 return
806 }
807 linkMessage, err := doCmd(fmt.Sprintf(LINK, hash, hash), true)
808 if err != nil {
commit-bot@chromium.org90041922014-04-22 21:13:45 +0000809 linkMessage = cleanCompileOutput(linkMessage, hash)
810 reportTryError(w, r, err, linkMessage, hash)
commit-bot@chromium.org6d036c22014-04-09 18:59:44 +0000811 return
812 }
813 message += linkMessage
814 cmd := hash + " --out " + hash + ".png"
commit-bot@chromium.org0a7e5b72014-05-29 15:58:00 +0000815 if request.Source > 0 {
816 cmd += fmt.Sprintf(" --source image-%d.png", request.Source)
817 }
commit-bot@chromium.org6d036c22014-04-09 18:59:44 +0000818 if *useChroot {
819 cmd = "schroot -c webtry --directory=/inout -- /inout/" + cmd
820 } else {
821 abs, err := filepath.Abs("../../../inout")
822 if err != nil {
commit-bot@chromium.org90041922014-04-22 21:13:45 +0000823 reportTryError(w, r, err, "Failed to find executable directory.", hash)
commit-bot@chromium.org6d036c22014-04-09 18:59:44 +0000824 return
825 }
826 cmd = abs + "/" + cmd
827 }
828
829 execMessage, err := doCmd(cmd, false)
830 if err != nil {
commit-bot@chromium.org90041922014-04-22 21:13:45 +0000831 reportTryError(w, r, err, "Failed to run the code:\n"+execMessage, hash)
commit-bot@chromium.org6d036c22014-04-09 18:59:44 +0000832 return
833 }
834 png, err := ioutil.ReadFile("../../../inout/" + hash + ".png")
835 if err != nil {
commit-bot@chromium.org90041922014-04-22 21:13:45 +0000836 reportTryError(w, r, err, "Failed to open the generated PNG.", hash)
commit-bot@chromium.org6d036c22014-04-09 18:59:44 +0000837 return
838 }
839
840 m := response{
841 Message: message,
commit-bot@chromium.org90041922014-04-22 21:13:45 +0000842 StdOut: execMessage,
commit-bot@chromium.org6d036c22014-04-09 18:59:44 +0000843 Img: base64.StdEncoding.EncodeToString([]byte(png)),
commit-bot@chromium.orgc81d1c42014-04-14 18:53:10 +0000844 Hash: hash,
commit-bot@chromium.org6d036c22014-04-09 18:59:44 +0000845 }
846 resp, err := json.Marshal(m)
847 if err != nil {
commit-bot@chromium.org90041922014-04-22 21:13:45 +0000848 reportTryError(w, r, err, "Failed to serialize a response.", hash)
commit-bot@chromium.org6d036c22014-04-09 18:59:44 +0000849 return
850 }
commit-bot@chromium.orga4c36772014-05-19 17:38:52 +0000851 w.Header().Set("Content-Type", "application/json")
commit-bot@chromium.org6d036c22014-04-09 18:59:44 +0000852 w.Write(resp)
853 }
854}
855
856func main() {
857 flag.Parse()
commit-bot@chromium.orga4c36772014-05-19 17:38:52 +0000858 http.HandleFunc("/i/", autogzip.HandleFunc(imageHandler))
859 http.HandleFunc("/w/", autogzip.HandleFunc(workspaceHandler))
860 http.HandleFunc("/recent/", autogzip.HandleFunc(recentHandler))
861 http.HandleFunc("/iframe/", autogzip.HandleFunc(iframeHandler))
862 http.HandleFunc("/json/", autogzip.HandleFunc(tryInfoHandler))
commit-bot@chromium.org0a7e5b72014-05-29 15:58:00 +0000863 http.HandleFunc("/sources/", autogzip.HandleFunc(sourcesHandler))
fmalita@google.com950306c2014-05-01 15:14:56 +0000864
865 // Resources are served directly
866 // TODO add support for caching/etags/gzip
commit-bot@chromium.orga4c36772014-05-19 17:38:52 +0000867 http.Handle("/res/", autogzip.Handle(http.FileServer(http.Dir("./"))))
fmalita@google.com950306c2014-05-01 15:14:56 +0000868
commit-bot@chromium.org35ffc442014-04-22 19:32:06 +0000869 // TODO Break out /c/ as it's own handler.
commit-bot@chromium.orga4c36772014-05-19 17:38:52 +0000870 http.HandleFunc("/", autogzip.HandleFunc(mainHandler))
commit-bot@chromium.org282333f2014-04-14 14:54:07 +0000871 log.Fatal(http.ListenAndServe(*port, nil))
commit-bot@chromium.org6d036c22014-04-09 18:59:44 +0000872}