OLD | NEW |
1 package main | 1 package main |
2 | 2 |
3 import ( | 3 import ( |
4 "bytes" | 4 "bytes" |
5 "crypto/md5" | 5 "crypto/md5" |
6 "database/sql" | 6 "database/sql" |
7 "encoding/base64" | 7 "encoding/base64" |
| 8 "encoding/binary" |
8 "encoding/json" | 9 "encoding/json" |
9 "flag" | 10 "flag" |
10 "fmt" | 11 "fmt" |
11 htemplate "html/template" | 12 htemplate "html/template" |
| 13 "image" |
| 14 _ "image/gif" |
| 15 _ "image/jpeg" |
| 16 "image/png" |
12 "io/ioutil" | 17 "io/ioutil" |
13 "log" | 18 "log" |
14 "math/rand" | 19 "math/rand" |
15 "net/http" | 20 "net/http" |
16 "os" | 21 "os" |
17 "os/exec" | 22 "os/exec" |
18 "path/filepath" | 23 "path/filepath" |
19 "regexp" | 24 "regexp" |
20 "strings" | 25 "strings" |
21 "text/template" | 26 "text/template" |
(...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
63 // db is the database, nil if we don't have an SQL database to store dat
a into. | 68 // db is the database, nil if we don't have an SQL database to store dat
a into. |
64 db *sql.DB = nil | 69 db *sql.DB = nil |
65 | 70 |
66 // directLink is the regex that matches URLs paths that are direct links
. | 71 // directLink is the regex that matches URLs paths that are direct links
. |
67 directLink = regexp.MustCompile("^/c/([a-f0-9]+)$") | 72 directLink = regexp.MustCompile("^/c/([a-f0-9]+)$") |
68 | 73 |
69 // iframeLink is the regex that matches URLs paths that are links to ifr
ames. | 74 // iframeLink is the regex that matches URLs paths that are links to ifr
ames. |
70 iframeLink = regexp.MustCompile("^/iframe/([a-f0-9]+)$") | 75 iframeLink = regexp.MustCompile("^/iframe/([a-f0-9]+)$") |
71 | 76 |
72 // imageLink is the regex that matches URLs paths that are direct links
to PNGs. | 77 // imageLink is the regex that matches URLs paths that are direct links
to PNGs. |
73 » imageLink = regexp.MustCompile("^/i/([a-f0-9]+.png)$") | 78 » imageLink = regexp.MustCompile("^/i/([a-z0-9-]+.png)$") |
74 | 79 |
75 // tryInfoLink is the regex that matches URLs paths that are direct link
s to data about a single try. | 80 // tryInfoLink is the regex that matches URLs paths that are direct link
s to data about a single try. |
76 tryInfoLink = regexp.MustCompile("^/json/([a-f0-9]+)$") | 81 tryInfoLink = regexp.MustCompile("^/json/([a-f0-9]+)$") |
77 | 82 |
78 // workspaceLink is the regex that matches URLs paths for workspaces. | 83 // workspaceLink is the regex that matches URLs paths for workspaces. |
79 workspaceLink = regexp.MustCompile("^/w/([a-z0-9-]+)$") | 84 workspaceLink = regexp.MustCompile("^/w/([a-z0-9-]+)$") |
80 | 85 |
81 // workspaceNameAdj is a list of adjectives for building workspace names
. | 86 // workspaceNameAdj is a list of adjectives for building workspace names
. |
82 workspaceNameAdj = []string{ | 87 workspaceNameAdj = []string{ |
83 "autumn", "hidden", "bitter", "misty", "silent", "empty", "dry",
"dark", | 88 "autumn", "hidden", "bitter", "misty", "silent", "empty", "dry",
"dark", |
(...skipping 130 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
214 panic(err) | 219 panic(err) |
215 } | 220 } |
216 } else { | 221 } else { |
217 log.Printf("INFO: Failed to find metadata, unable to connect to
MySQL server (Expected when running locally): %q\n", err) | 222 log.Printf("INFO: Failed to find metadata, unable to connect to
MySQL server (Expected when running locally): %q\n", err) |
218 // Fallback to sqlite for local use. | 223 // Fallback to sqlite for local use. |
219 db, err = sql.Open("sqlite3", "./webtry.db") | 224 db, err = sql.Open("sqlite3", "./webtry.db") |
220 if err != nil { | 225 if err != nil { |
221 log.Printf("ERROR: Failed to open: %q\n", err) | 226 log.Printf("ERROR: Failed to open: %q\n", err) |
222 panic(err) | 227 panic(err) |
223 } | 228 } |
224 » » sql := `CREATE TABLE webtry ( | 229 » » sql := `CREATE TABLE source_images ( |
225 code TEXT DEFAULT '' NOT NULL, | 230 id INTEGER PRIMARY KEY NOT NULL, |
226 create_ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, | 231 image MEDIUMBLOB DEFAULT '' NOT NULL, -- forma
tted as a PNG. |
227 hash CHAR(64) DEFAULT '' NOT NULL, | 232 width INTEGER DEFAULT 0 NOT NULL, |
| 233 height INTEGER DEFAULT 0 NOT NULL, |
| 234 create_ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, |
| 235 hidden INTEGER DEFAULT 0 NOT NULL |
| 236 )` |
| 237 » » _, err = db.Exec(sql) |
| 238 » » log.Printf("Info: status creating sqlite table for sources: %q\n
", err) |
| 239 |
| 240 » » sql = `CREATE TABLE webtry ( |
| 241 code TEXT DEFAULT '' NOT NULL, |
| 242 create_ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, |
| 243 hash CHAR(64) DEFAULT '' NOT NULL, |
| 244 source_image_id INTEGER DEFAULT 0 NOT NULL, |
| 245 |
228 PRIMARY KEY(hash) | 246 PRIMARY KEY(hash) |
229 )` | 247 )` |
230 _, err = db.Exec(sql) | 248 _, err = db.Exec(sql) |
231 log.Printf("Info: status creating sqlite table for webtry: %q\n"
, err) | 249 log.Printf("Info: status creating sqlite table for webtry: %q\n"
, err) |
| 250 |
232 sql = `CREATE TABLE workspace ( | 251 sql = `CREATE TABLE workspace ( |
233 name CHAR(64) DEFAULT '' NOT NULL, | 252 name CHAR(64) DEFAULT '' NOT NULL, |
234 create_ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, | 253 create_ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, |
235 PRIMARY KEY(name) | 254 PRIMARY KEY(name) |
236 )` | 255 )` |
237 _, err = db.Exec(sql) | 256 _, err = db.Exec(sql) |
238 log.Printf("Info: status creating sqlite table for workspace: %q
\n", err) | 257 log.Printf("Info: status creating sqlite table for workspace: %q
\n", err) |
| 258 |
239 sql = `CREATE TABLE workspacetry ( | 259 sql = `CREATE TABLE workspacetry ( |
240 name CHAR(64) DEFAULT '' NOT NULL, | 260 name CHAR(64) DEFAULT '' NOT NULL, |
241 create_ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, | 261 create_ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, |
242 hash CHAR(64) DEFAULT '' NOT NULL, | 262 hash CHAR(64) DEFAULT '' NOT NULL, |
243 hidden INTEGER DEFAULT 0 NOT NULL, | 263 hidden INTEGER DEFAULT 0 NOT NULL, |
| 264 source_image_id INTEGER DEFAULT 0 NOT NULL, |
244 | 265 |
245 FOREIGN KEY (name) REFERENCES workspace(name) | 266 FOREIGN KEY (name) REFERENCES workspace(name) |
246 )` | 267 )` |
247 _, err = db.Exec(sql) | 268 _, err = db.Exec(sql) |
248 log.Printf("Info: status creating sqlite table for workspace try
: %q\n", err) | 269 log.Printf("Info: status creating sqlite table for workspace try
: %q\n", err) |
249 } | 270 } |
250 | 271 |
251 // Ping the database to keep the connection fresh. | 272 // Ping the database to keep the connection fresh. |
252 go func() { | 273 go func() { |
253 c := time.Tick(1 * time.Minute) | 274 c := time.Tick(1 * time.Minute) |
254 for _ = range c { | 275 for _ = range c { |
255 if err := db.Ping(); err != nil { | 276 if err := db.Ping(); err != nil { |
256 log.Printf("ERROR: Database failed to respond: %
q\n", err) | 277 log.Printf("ERROR: Database failed to respond: %
q\n", err) |
257 } | 278 } |
258 } | 279 } |
259 }() | 280 }() |
260 | 281 |
| 282 writeOutAllSourceImages() |
| 283 } |
| 284 |
| 285 func writeOutAllSourceImages() { |
| 286 // Pull all the source images from the db and write them out to inout. |
| 287 rows, err := db.Query("SELECT id, image, create_ts FROM source_images OR
DER BY create_ts DESC") |
| 288 |
| 289 if err != nil { |
| 290 log.Printf("ERROR: Failed to open connection to SQL server: %q\n
", err) |
| 291 panic(err) |
| 292 } |
| 293 for rows.Next() { |
| 294 var id int |
| 295 var image []byte |
| 296 var create_ts time.Time |
| 297 if err := rows.Scan(&id, &image, &create_ts); err != nil { |
| 298 log.Printf("Error: failed to fetch from database: %q", e
rr) |
| 299 continue |
| 300 } |
| 301 filename := fmt.Sprintf("../../../inout/image-%d.png", id) |
| 302 if _, err := os.Stat(filename); os.IsExist(err) { |
| 303 log.Printf("Skipping write since file exists: %q", filen
ame) |
| 304 continue |
| 305 } |
| 306 if err := ioutil.WriteFile(filename, image, 0666); err != nil { |
| 307 log.Printf("Error: failed to write image file: %q", err) |
| 308 } |
| 309 } |
261 } | 310 } |
262 | 311 |
263 // Titlebar is used in titlebar template expansion. | 312 // Titlebar is used in titlebar template expansion. |
264 type Titlebar struct { | 313 type Titlebar struct { |
265 GitHash string | 314 GitHash string |
266 GitInfo string | 315 GitInfo string |
267 } | 316 } |
268 | 317 |
269 // userCode is used in template expansion. | 318 // userCode is used in template expansion. |
270 type userCode struct { | 319 type userCode struct { |
271 Code string | 320 Code string |
272 Hash string | 321 Hash string |
| 322 Source int |
273 Titlebar Titlebar | 323 Titlebar Titlebar |
274 } | 324 } |
275 | 325 |
276 // expandToFile expands the template and writes the result to the file. | 326 // expandToFile expands the template and writes the result to the file. |
277 func expandToFile(filename string, code string, t *template.Template) error { | 327 func expandToFile(filename string, code string, t *template.Template) error { |
278 f, err := os.Create(filename) | 328 f, err := os.Create(filename) |
279 if err != nil { | 329 if err != nil { |
280 return err | 330 return err |
281 } | 331 } |
282 defer f.Close() | 332 defer f.Close() |
283 return t.Execute(f, userCode{Code: code, Titlebar: Titlebar{GitHash: git
Hash, GitInfo: gitInfo}}) | 333 return t.Execute(f, userCode{Code: code, Titlebar: Titlebar{GitHash: git
Hash, GitInfo: gitInfo}}) |
284 } | 334 } |
285 | 335 |
286 // expandCode expands the template into a file and calculate the MD5 hash. | 336 // expandCode expands the template into a file and calculates the MD5 hash. |
287 func expandCode(code string) (string, error) { | 337 func expandCode(code string, source int) (string, error) { |
288 h := md5.New() | 338 h := md5.New() |
289 h.Write([]byte(code)) | 339 h.Write([]byte(code)) |
| 340 binary.Write(h, binary.LittleEndian, int64(source)) |
290 hash := fmt.Sprintf("%x", h.Sum(nil)) | 341 hash := fmt.Sprintf("%x", h.Sum(nil)) |
291 // At this point we are running in skia/experimental/webtry, making cach
e a | 342 // At this point we are running in skia/experimental/webtry, making cach
e a |
292 // peer directory to skia. | 343 // peer directory to skia. |
293 // TODO(jcgregorio) Make all relative directories into flags. | 344 // TODO(jcgregorio) Make all relative directories into flags. |
294 err := expandToFile(fmt.Sprintf("../../../cache/%s.cpp", hash), code, co
deTemplate) | 345 err := expandToFile(fmt.Sprintf("../../../cache/%s.cpp", hash), code, co
deTemplate) |
295 return hash, err | 346 return hash, err |
296 } | 347 } |
297 | 348 |
298 // response is serialized to JSON as a response to POSTs. | 349 // response is serialized to JSON as a response to POSTs. |
299 type response struct { | 350 type response struct { |
(...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
353 log.Printf("Error: %s\n%s", message, err.Error()) | 404 log.Printf("Error: %s\n%s", message, err.Error()) |
354 resp, err := json.Marshal(m) | 405 resp, err := json.Marshal(m) |
355 if err != nil { | 406 if err != nil { |
356 http.Error(w, "Failed to serialize a response", 500) | 407 http.Error(w, "Failed to serialize a response", 500) |
357 return | 408 return |
358 } | 409 } |
359 w.Header().Set("Content-Type", "text/plain") | 410 w.Header().Set("Content-Type", "text/plain") |
360 w.Write(resp) | 411 w.Write(resp) |
361 } | 412 } |
362 | 413 |
363 func writeToDatabase(hash string, code string, workspaceName string) { | 414 func writeToDatabase(hash string, code string, workspaceName string, source int)
{ |
364 if db == nil { | 415 if db == nil { |
365 return | 416 return |
366 } | 417 } |
367 » if _, err := db.Exec("INSERT INTO webtry (code, hash) VALUES(?, ?)", cod
e, hash); err != nil { | 418 » if _, err := db.Exec("INSERT INTO webtry (code, hash, source_image_id) V
ALUES(?, ?, ?)", code, hash, source); err != nil { |
368 log.Printf("ERROR: Failed to insert code into database: %q\n", e
rr) | 419 log.Printf("ERROR: Failed to insert code into database: %q\n", e
rr) |
369 } | 420 } |
370 if workspaceName != "" { | 421 if workspaceName != "" { |
371 » » if _, err := db.Exec("INSERT INTO workspacetry (name, hash) VALU
ES(?, ?)", workspaceName, hash); err != nil { | 422 » » if _, err := db.Exec("INSERT INTO workspacetry (name, hash, sour
ce_image_id) VALUES(?, ?, ?)", workspaceName, hash, source); err != nil { |
372 log.Printf("ERROR: Failed to insert into workspacetry ta
ble: %q\n", err) | 423 log.Printf("ERROR: Failed to insert into workspacetry ta
ble: %q\n", err) |
373 } | 424 } |
374 } | 425 } |
375 } | 426 } |
376 | 427 |
| 428 type Sources struct { |
| 429 Id int `json:"id"` |
| 430 } |
| 431 |
| 432 // sourcesHandler serves up the PNG of a specific try. |
| 433 func sourcesHandler(w http.ResponseWriter, r *http.Request) { |
| 434 log.Printf("Sources Handler: %q\n", r.URL.Path) |
| 435 if r.Method == "GET" { |
| 436 rows, err := db.Query("SELECT id, create_ts FROM source_images W
HERE hidden=0 ORDER BY create_ts DESC") |
| 437 |
| 438 if err != nil { |
| 439 http.Error(w, fmt.Sprintf("Failed to query sources: %s."
, err), 500) |
| 440 } |
| 441 sources := make([]Sources, 0, 0) |
| 442 for rows.Next() { |
| 443 var id int |
| 444 var create_ts time.Time |
| 445 if err := rows.Scan(&id, &create_ts); err != nil { |
| 446 log.Printf("Error: failed to fetch from database
: %q", err) |
| 447 continue |
| 448 } |
| 449 sources = append(sources, Sources{Id: id}) |
| 450 } |
| 451 |
| 452 resp, err := json.Marshal(sources) |
| 453 if err != nil { |
| 454 reportError(w, r, err, "Failed to serialize a response."
) |
| 455 return |
| 456 } |
| 457 w.Header().Set("Content-Type", "application/json") |
| 458 w.Write(resp) |
| 459 |
| 460 } else if r.Method == "POST" { |
| 461 if err := r.ParseMultipartForm(1000000); err != nil { |
| 462 http.Error(w, fmt.Sprintf("Failed to load image: %s.", e
rr), 500) |
| 463 return |
| 464 } |
| 465 if _, ok := r.MultipartForm.File["upload"]; !ok { |
| 466 http.Error(w, "Invalid upload.", 500) |
| 467 return |
| 468 } |
| 469 if len(r.MultipartForm.File["upload"]) != 1 { |
| 470 http.Error(w, "Wrong number of uploads.", 500) |
| 471 return |
| 472 } |
| 473 f, err := r.MultipartForm.File["upload"][0].Open() |
| 474 if err != nil { |
| 475 http.Error(w, fmt.Sprintf("Failed to load image: %s.", e
rr), 500) |
| 476 return |
| 477 } |
| 478 defer f.Close() |
| 479 m, _, err := image.Decode(f) |
| 480 if err != nil { |
| 481 http.Error(w, fmt.Sprintf("Failed to decode image: %s.",
err), 500) |
| 482 return |
| 483 } |
| 484 var b bytes.Buffer |
| 485 png.Encode(&b, m) |
| 486 bounds := m.Bounds() |
| 487 width := bounds.Max.Y - bounds.Min.Y |
| 488 height := bounds.Max.X - bounds.Min.X |
| 489 if _, err := db.Exec("INSERT INTO source_images (image, width, h
eight) VALUES(?, ?, ?)", b.Bytes(), width, height); err != nil { |
| 490 log.Printf("ERROR: Failed to insert sources into databas
e: %q\n", err) |
| 491 http.Error(w, fmt.Sprintf("Failed to store image: %s.",
err), 500) |
| 492 return |
| 493 } |
| 494 go writeOutAllSourceImages() |
| 495 |
| 496 // Now redirect back to where we came from. |
| 497 http.Redirect(w, r, r.Referer(), 302) |
| 498 } else { |
| 499 http.NotFound(w, r) |
| 500 return |
| 501 } |
| 502 } |
| 503 |
377 // imageHandler serves up the PNG of a specific try. | 504 // imageHandler serves up the PNG of a specific try. |
378 func imageHandler(w http.ResponseWriter, r *http.Request) { | 505 func imageHandler(w http.ResponseWriter, r *http.Request) { |
379 log.Printf("Image Handler: %q\n", r.URL.Path) | 506 log.Printf("Image Handler: %q\n", r.URL.Path) |
380 if r.Method != "GET" { | 507 if r.Method != "GET" { |
381 http.NotFound(w, r) | 508 http.NotFound(w, r) |
382 return | 509 return |
383 } | 510 } |
384 match := imageLink.FindStringSubmatch(r.URL.Path) | 511 match := imageLink.FindStringSubmatch(r.URL.Path) |
385 if len(match) != 2 { | 512 if len(match) != 2 { |
386 http.NotFound(w, r) | 513 http.NotFound(w, r) |
387 return | 514 return |
388 } | 515 } |
389 filename := match[1] | 516 filename := match[1] |
390 w.Header().Set("Content-Type", "image/png") | 517 w.Header().Set("Content-Type", "image/png") |
391 http.ServeFile(w, r, fmt.Sprintf("../../../inout/%s", filename)) | 518 http.ServeFile(w, r, fmt.Sprintf("../../../inout/%s", filename)) |
392 } | 519 } |
393 | 520 |
394 type Try struct { | 521 type Try struct { |
395 Hash string `json:"hash"` | 522 Hash string `json:"hash"` |
| 523 Source int |
396 CreateTS string `json:"create_ts"` | 524 CreateTS string `json:"create_ts"` |
397 } | 525 } |
398 | 526 |
399 type Recent struct { | 527 type Recent struct { |
400 Tries []Try | 528 Tries []Try |
401 Titlebar Titlebar | 529 Titlebar Titlebar |
402 } | 530 } |
403 | 531 |
404 // recentHandler shows the last 20 tries. | 532 // recentHandler shows the last 20 tries. |
405 func recentHandler(w http.ResponseWriter, r *http.Request) { | 533 func recentHandler(w http.ResponseWriter, r *http.Request) { |
(...skipping 18 matching lines...) Expand all Loading... |
424 w.Header().Set("Content-Type", "text/html") | 552 w.Header().Set("Content-Type", "text/html") |
425 if err := recentTemplate.Execute(w, Recent{Tries: recent, Titlebar: Titl
ebar{GitHash: gitHash, GitInfo: gitInfo}}); err != nil { | 553 if err := recentTemplate.Execute(w, Recent{Tries: recent, Titlebar: Titl
ebar{GitHash: gitHash, GitInfo: gitInfo}}); err != nil { |
426 log.Printf("ERROR: Failed to expand template: %q\n", err) | 554 log.Printf("ERROR: Failed to expand template: %q\n", err) |
427 } | 555 } |
428 } | 556 } |
429 | 557 |
430 type Workspace struct { | 558 type Workspace struct { |
431 Name string | 559 Name string |
432 Code string | 560 Code string |
433 Hash string | 561 Hash string |
| 562 Source int |
434 Tries []Try | 563 Tries []Try |
435 Titlebar Titlebar | 564 Titlebar Titlebar |
436 } | 565 } |
437 | 566 |
438 // newWorkspace generates a new random workspace name and stores it in the datab
ase. | 567 // newWorkspace generates a new random workspace name and stores it in the datab
ase. |
439 func newWorkspace() (string, error) { | 568 func newWorkspace() (string, error) { |
440 for i := 0; i < 10; i++ { | 569 for i := 0; i < 10; i++ { |
441 adj := workspaceNameAdj[rand.Intn(len(workspaceNameAdj))] | 570 adj := workspaceNameAdj[rand.Intn(len(workspaceNameAdj))] |
442 noun := workspaceNameNoun[rand.Intn(len(workspaceNameNoun))] | 571 noun := workspaceNameNoun[rand.Intn(len(workspaceNameNoun))] |
443 suffix := rand.Intn(1000) | 572 suffix := rand.Intn(1000) |
444 name := fmt.Sprintf("%s-%s-%d", adj, noun, suffix) | 573 name := fmt.Sprintf("%s-%s-%d", adj, noun, suffix) |
445 if _, err := db.Exec("INSERT INTO workspace (name) VALUES(?)", n
ame); err == nil { | 574 if _, err := db.Exec("INSERT INTO workspace (name) VALUES(?)", n
ame); err == nil { |
446 return name, nil | 575 return name, nil |
447 } else { | 576 } else { |
448 log.Printf("ERROR: Failed to insert workspace into datab
ase: %q\n", err) | 577 log.Printf("ERROR: Failed to insert workspace into datab
ase: %q\n", err) |
449 } | 578 } |
450 } | 579 } |
451 return "", fmt.Errorf("Failed to create a new workspace") | 580 return "", fmt.Errorf("Failed to create a new workspace") |
452 } | 581 } |
453 | 582 |
454 // getCode returns the code for a given hash, or the empty string if not found. | 583 // getCode returns the code for a given hash, or the empty string if not found. |
455 func getCode(hash string) (string, error) { | 584 func getCode(hash string) (string, int, error) { |
456 code := "" | 585 code := "" |
457 » if err := db.QueryRow("SELECT code FROM webtry WHERE hash=?", hash).Scan
(&code); err != nil { | 586 » source := 0 |
| 587 » if err := db.QueryRow("SELECT code, source_image_id FROM webtry WHERE ha
sh=?", hash).Scan(&code, &source); err != nil { |
458 log.Printf("ERROR: Code for hash is missing: %q\n", err) | 588 log.Printf("ERROR: Code for hash is missing: %q\n", err) |
459 » » return code, err | 589 » » return code, source, err |
460 } | 590 } |
461 » return code, nil | 591 » return code, source, nil |
462 } | 592 } |
463 | 593 |
464 func workspaceHandler(w http.ResponseWriter, r *http.Request) { | 594 func workspaceHandler(w http.ResponseWriter, r *http.Request) { |
465 log.Printf("Workspace Handler: %q\n", r.URL.Path) | 595 log.Printf("Workspace Handler: %q\n", r.URL.Path) |
466 if r.Method == "GET" { | 596 if r.Method == "GET" { |
467 tries := []Try{} | 597 tries := []Try{} |
468 match := workspaceLink.FindStringSubmatch(r.URL.Path) | 598 match := workspaceLink.FindStringSubmatch(r.URL.Path) |
469 name := "" | 599 name := "" |
470 if len(match) == 2 { | 600 if len(match) == 2 { |
471 name = match[1] | 601 name = match[1] |
472 » » » rows, err := db.Query("SELECT create_ts, hash FROM works
pacetry WHERE name=? ORDER BY create_ts", name) | 602 » » » rows, err := db.Query("SELECT create_ts, hash, source_im
age_id FROM workspacetry WHERE name=? ORDER BY create_ts", name) |
473 if err != nil { | 603 if err != nil { |
474 reportError(w, r, err, "Failed to select.") | 604 reportError(w, r, err, "Failed to select.") |
475 return | 605 return |
476 } | 606 } |
477 for rows.Next() { | 607 for rows.Next() { |
478 var hash string | 608 var hash string |
479 var create_ts time.Time | 609 var create_ts time.Time |
480 » » » » if err := rows.Scan(&create_ts, &hash); err != n
il { | 610 » » » » var source int |
| 611 » » » » if err := rows.Scan(&create_ts, &hash, &source);
err != nil { |
481 log.Printf("Error: failed to fetch from
database: %q", err) | 612 log.Printf("Error: failed to fetch from
database: %q", err) |
482 continue | 613 continue |
483 } | 614 } |
484 » » » » tries = append(tries, Try{Hash: hash, CreateTS:
create_ts.Format("2006-02-01")}) | 615 » » » » tries = append(tries, Try{Hash: hash, Source: so
urce, CreateTS: create_ts.Format("2006-02-01")}) |
485 } | 616 } |
486 } | 617 } |
487 var code string | 618 var code string |
488 var hash string | 619 var hash string |
| 620 source := 0 |
489 if len(tries) == 0 { | 621 if len(tries) == 0 { |
490 code = DEFAULT_SAMPLE | 622 code = DEFAULT_SAMPLE |
491 } else { | 623 } else { |
492 hash = tries[len(tries)-1].Hash | 624 hash = tries[len(tries)-1].Hash |
493 » » » code, _ = getCode(hash) | 625 » » » code, source, _ = getCode(hash) |
494 } | 626 } |
495 w.Header().Set("Content-Type", "text/html") | 627 w.Header().Set("Content-Type", "text/html") |
496 » » if err := workspaceTemplate.Execute(w, Workspace{Tries: tries, C
ode: code, Name: name, Hash: hash, Titlebar: Titlebar{GitHash: gitHash, GitInfo:
gitInfo}}); err != nil { | 628 » » if err := workspaceTemplate.Execute(w, Workspace{Tries: tries, C
ode: code, Name: name, Hash: hash, Source: source, Titlebar: Titlebar{GitHash: g
itHash, GitInfo: gitInfo}}); err != nil { |
497 log.Printf("ERROR: Failed to expand template: %q\n", err
) | 629 log.Printf("ERROR: Failed to expand template: %q\n", err
) |
498 } | 630 } |
499 } else if r.Method == "POST" { | 631 } else if r.Method == "POST" { |
500 name, err := newWorkspace() | 632 name, err := newWorkspace() |
501 if err != nil { | 633 if err != nil { |
502 http.Error(w, "Failed to create a new workspace.", 500) | 634 http.Error(w, "Failed to create a new workspace.", 500) |
503 return | 635 return |
504 } | 636 } |
505 http.Redirect(w, r, "/w/"+name, 302) | 637 http.Redirect(w, r, "/w/"+name, 302) |
506 } | 638 } |
507 } | 639 } |
508 | 640 |
509 // hasPreProcessor returns true if any line in the code begins with a # char. | 641 // hasPreProcessor returns true if any line in the code begins with a # char. |
510 func hasPreProcessor(code string) bool { | 642 func hasPreProcessor(code string) bool { |
511 lines := strings.Split(code, "\n") | 643 lines := strings.Split(code, "\n") |
512 for _, s := range lines { | 644 for _, s := range lines { |
513 if strings.HasPrefix(strings.TrimSpace(s), "#") { | 645 if strings.HasPrefix(strings.TrimSpace(s), "#") { |
514 return true | 646 return true |
515 } | 647 } |
516 } | 648 } |
517 return false | 649 return false |
518 } | 650 } |
519 | 651 |
520 type TryRequest struct { | 652 type TryRequest struct { |
521 » Code string `json:"code"` | 653 » Code string `json:"code"` |
522 » Name string `json:"name"` // Optional name of the workspace the code is
in. | 654 » Name string `json:"name"` // Optional name of the workspace the code
is in. |
| 655 » Source int `json:"source"` // ID of the source image, 0 if none. |
523 } | 656 } |
524 | 657 |
525 // iframeHandler handles the GET and POST of the main page. | 658 // iframeHandler handles the GET and POST of the main page. |
526 func iframeHandler(w http.ResponseWriter, r *http.Request) { | 659 func iframeHandler(w http.ResponseWriter, r *http.Request) { |
527 log.Printf("IFrame Handler: %q\n", r.URL.Path) | 660 log.Printf("IFrame Handler: %q\n", r.URL.Path) |
528 if r.Method != "GET" { | 661 if r.Method != "GET" { |
529 http.NotFound(w, r) | 662 http.NotFound(w, r) |
530 return | 663 return |
531 } | 664 } |
532 match := iframeLink.FindStringSubmatch(r.URL.Path) | 665 match := iframeLink.FindStringSubmatch(r.URL.Path) |
533 if len(match) != 2 { | 666 if len(match) != 2 { |
534 http.NotFound(w, r) | 667 http.NotFound(w, r) |
535 return | 668 return |
536 } | 669 } |
537 hash := match[1] | 670 hash := match[1] |
538 if db == nil { | 671 if db == nil { |
539 http.NotFound(w, r) | 672 http.NotFound(w, r) |
540 return | 673 return |
541 } | 674 } |
542 var code string | 675 var code string |
543 » code, err := getCode(hash) | 676 » code, source, err := getCode(hash) |
544 if err != nil { | 677 if err != nil { |
545 http.NotFound(w, r) | 678 http.NotFound(w, r) |
546 return | 679 return |
547 } | 680 } |
548 // Expand the template. | 681 // Expand the template. |
549 w.Header().Set("Content-Type", "text/html") | 682 w.Header().Set("Content-Type", "text/html") |
550 » if err := iframeTemplate.Execute(w, userCode{Code: code, Hash: hash}); e
rr != nil { | 683 » if err := iframeTemplate.Execute(w, userCode{Code: code, Hash: hash, Sou
rce: source}); err != nil { |
551 log.Printf("ERROR: Failed to expand template: %q\n", err) | 684 log.Printf("ERROR: Failed to expand template: %q\n", err) |
552 } | 685 } |
553 } | 686 } |
554 | 687 |
555 type TryInfo struct { | 688 type TryInfo struct { |
556 » Hash string `json:"hash"` | 689 » Hash string `json:"hash"` |
557 » Code string `json:"code"` | 690 » Code string `json:"code"` |
| 691 » Source int `json:"source"` |
558 } | 692 } |
559 | 693 |
560 // tryInfoHandler returns information about a specific try. | 694 // tryInfoHandler returns information about a specific try. |
561 func tryInfoHandler(w http.ResponseWriter, r *http.Request) { | 695 func tryInfoHandler(w http.ResponseWriter, r *http.Request) { |
562 log.Printf("Try Info Handler: %q\n", r.URL.Path) | 696 log.Printf("Try Info Handler: %q\n", r.URL.Path) |
563 if r.Method != "GET" { | 697 if r.Method != "GET" { |
564 http.NotFound(w, r) | 698 http.NotFound(w, r) |
565 return | 699 return |
566 } | 700 } |
567 match := tryInfoLink.FindStringSubmatch(r.URL.Path) | 701 match := tryInfoLink.FindStringSubmatch(r.URL.Path) |
568 if len(match) != 2 { | 702 if len(match) != 2 { |
569 http.NotFound(w, r) | 703 http.NotFound(w, r) |
570 return | 704 return |
571 } | 705 } |
572 hash := match[1] | 706 hash := match[1] |
573 » code, err := getCode(hash) | 707 » code, source, err := getCode(hash) |
574 if err != nil { | 708 if err != nil { |
575 http.NotFound(w, r) | 709 http.NotFound(w, r) |
576 return | 710 return |
577 } | 711 } |
578 m := TryInfo{ | 712 m := TryInfo{ |
579 » » Hash: hash, | 713 » » Hash: hash, |
580 » » Code: code, | 714 » » Code: code, |
| 715 » » Source: source, |
581 } | 716 } |
582 resp, err := json.Marshal(m) | 717 resp, err := json.Marshal(m) |
583 if err != nil { | 718 if err != nil { |
584 reportError(w, r, err, "Failed to serialize a response.") | 719 reportError(w, r, err, "Failed to serialize a response.") |
585 return | 720 return |
586 } | 721 } |
587 w.Header().Set("Content-Type", "application/json") | 722 w.Header().Set("Content-Type", "application/json") |
588 w.Write(resp) | 723 w.Write(resp) |
589 } | 724 } |
590 | 725 |
591 func cleanCompileOutput(s, hash string) string { | 726 func cleanCompileOutput(s, hash string) string { |
592 old := "../../../cache/" + hash + ".cpp:" | 727 old := "../../../cache/" + hash + ".cpp:" |
593 log.Printf("INFO: replacing %q\n", old) | 728 log.Printf("INFO: replacing %q\n", old) |
594 return strings.Replace(s, old, "usercode.cpp:", -1) | 729 return strings.Replace(s, old, "usercode.cpp:", -1) |
595 } | 730 } |
596 | 731 |
597 // mainHandler handles the GET and POST of the main page. | 732 // mainHandler handles the GET and POST of the main page. |
598 func mainHandler(w http.ResponseWriter, r *http.Request) { | 733 func mainHandler(w http.ResponseWriter, r *http.Request) { |
599 log.Printf("Main Handler: %q\n", r.URL.Path) | 734 log.Printf("Main Handler: %q\n", r.URL.Path) |
600 if r.Method == "GET" { | 735 if r.Method == "GET" { |
601 code := DEFAULT_SAMPLE | 736 code := DEFAULT_SAMPLE |
| 737 source := 0 |
602 match := directLink.FindStringSubmatch(r.URL.Path) | 738 match := directLink.FindStringSubmatch(r.URL.Path) |
603 var hash string | 739 var hash string |
604 if len(match) == 2 && r.URL.Path != "/" { | 740 if len(match) == 2 && r.URL.Path != "/" { |
605 hash = match[1] | 741 hash = match[1] |
606 if db == nil { | 742 if db == nil { |
607 http.NotFound(w, r) | 743 http.NotFound(w, r) |
608 return | 744 return |
609 } | 745 } |
610 // Update 'code' with the code found in the database. | 746 // Update 'code' with the code found in the database. |
611 » » » if err := db.QueryRow("SELECT code FROM webtry WHERE has
h=?", hash).Scan(&code); err != nil { | 747 » » » if err := db.QueryRow("SELECT code, source_image_id FROM
webtry WHERE hash=?", hash).Scan(&code, &source); err != nil { |
612 http.NotFound(w, r) | 748 http.NotFound(w, r) |
613 return | 749 return |
614 } | 750 } |
615 } | 751 } |
616 // Expand the template. | 752 // Expand the template. |
617 w.Header().Set("Content-Type", "text/html") | 753 w.Header().Set("Content-Type", "text/html") |
618 » » if err := indexTemplate.Execute(w, userCode{Code: code, Hash: ha
sh, Titlebar: Titlebar{GitHash: gitHash, GitInfo: gitInfo}}); err != nil { | 754 » » if err := indexTemplate.Execute(w, userCode{Code: code, Hash: ha
sh, Source: source, Titlebar: Titlebar{GitHash: gitHash, GitInfo: gitInfo}}); er
r != nil { |
619 log.Printf("ERROR: Failed to expand template: %q\n", err
) | 755 log.Printf("ERROR: Failed to expand template: %q\n", err
) |
620 } | 756 } |
621 } else if r.Method == "POST" { | 757 } else if r.Method == "POST" { |
622 w.Header().Set("Content-Type", "application/json") | 758 w.Header().Set("Content-Type", "application/json") |
623 buf := bytes.NewBuffer(make([]byte, 0, MAX_TRY_SIZE)) | 759 buf := bytes.NewBuffer(make([]byte, 0, MAX_TRY_SIZE)) |
624 n, err := buf.ReadFrom(r.Body) | 760 n, err := buf.ReadFrom(r.Body) |
625 if err != nil { | 761 if err != nil { |
626 reportTryError(w, r, err, "Failed to read a request body
.", "") | 762 reportTryError(w, r, err, "Failed to read a request body
.", "") |
627 return | 763 return |
628 } | 764 } |
629 if n == MAX_TRY_SIZE { | 765 if n == MAX_TRY_SIZE { |
630 err := fmt.Errorf("Code length equal to, or exceeded, %d
", MAX_TRY_SIZE) | 766 err := fmt.Errorf("Code length equal to, or exceeded, %d
", MAX_TRY_SIZE) |
631 reportTryError(w, r, err, "Code too large.", "") | 767 reportTryError(w, r, err, "Code too large.", "") |
632 return | 768 return |
633 } | 769 } |
634 request := TryRequest{} | 770 request := TryRequest{} |
635 if err := json.Unmarshal(buf.Bytes(), &request); err != nil { | 771 if err := json.Unmarshal(buf.Bytes(), &request); err != nil { |
636 reportTryError(w, r, err, "Coulnd't decode JSON.", "") | 772 reportTryError(w, r, err, "Coulnd't decode JSON.", "") |
637 return | 773 return |
638 } | 774 } |
639 if hasPreProcessor(request.Code) { | 775 if hasPreProcessor(request.Code) { |
640 err := fmt.Errorf("Found preprocessor macro in code.") | 776 err := fmt.Errorf("Found preprocessor macro in code.") |
641 reportTryError(w, r, err, "Preprocessor macros aren't al
lowed.", "") | 777 reportTryError(w, r, err, "Preprocessor macros aren't al
lowed.", "") |
642 return | 778 return |
643 } | 779 } |
644 » » hash, err := expandCode(LineNumbers(request.Code)) | 780 » » hash, err := expandCode(LineNumbers(request.Code), request.Sourc
e) |
645 if err != nil { | 781 if err != nil { |
646 reportTryError(w, r, err, "Failed to write the code to c
ompile.", hash) | 782 reportTryError(w, r, err, "Failed to write the code to c
ompile.", hash) |
647 return | 783 return |
648 } | 784 } |
649 » » writeToDatabase(hash, request.Code, request.Name) | 785 » » writeToDatabase(hash, request.Code, request.Name, request.Source
) |
650 message, err := doCmd(fmt.Sprintf(RESULT_COMPILE, hash, hash), t
rue) | 786 message, err := doCmd(fmt.Sprintf(RESULT_COMPILE, hash, hash), t
rue) |
651 if err != nil { | 787 if err != nil { |
652 message = cleanCompileOutput(message, hash) | 788 message = cleanCompileOutput(message, hash) |
653 reportTryError(w, r, err, message, hash) | 789 reportTryError(w, r, err, message, hash) |
654 return | 790 return |
655 } | 791 } |
656 linkMessage, err := doCmd(fmt.Sprintf(LINK, hash, hash), true) | 792 linkMessage, err := doCmd(fmt.Sprintf(LINK, hash, hash), true) |
657 if err != nil { | 793 if err != nil { |
658 linkMessage = cleanCompileOutput(linkMessage, hash) | 794 linkMessage = cleanCompileOutput(linkMessage, hash) |
659 reportTryError(w, r, err, linkMessage, hash) | 795 reportTryError(w, r, err, linkMessage, hash) |
660 return | 796 return |
661 } | 797 } |
662 message += linkMessage | 798 message += linkMessage |
663 cmd := hash + " --out " + hash + ".png" | 799 cmd := hash + " --out " + hash + ".png" |
| 800 if request.Source > 0 { |
| 801 cmd += fmt.Sprintf(" --source image-%d.png", request.So
urce) |
| 802 } |
664 if *useChroot { | 803 if *useChroot { |
665 cmd = "schroot -c webtry --directory=/inout -- /inout/"
+ cmd | 804 cmd = "schroot -c webtry --directory=/inout -- /inout/"
+ cmd |
666 } else { | 805 } else { |
667 abs, err := filepath.Abs("../../../inout") | 806 abs, err := filepath.Abs("../../../inout") |
668 if err != nil { | 807 if err != nil { |
669 reportTryError(w, r, err, "Failed to find execut
able directory.", hash) | 808 reportTryError(w, r, err, "Failed to find execut
able directory.", hash) |
670 return | 809 return |
671 } | 810 } |
672 cmd = abs + "/" + cmd | 811 cmd = abs + "/" + cmd |
673 } | 812 } |
(...skipping 25 matching lines...) Expand all Loading... |
699 } | 838 } |
700 } | 839 } |
701 | 840 |
702 func main() { | 841 func main() { |
703 flag.Parse() | 842 flag.Parse() |
704 http.HandleFunc("/i/", autogzip.HandleFunc(imageHandler)) | 843 http.HandleFunc("/i/", autogzip.HandleFunc(imageHandler)) |
705 http.HandleFunc("/w/", autogzip.HandleFunc(workspaceHandler)) | 844 http.HandleFunc("/w/", autogzip.HandleFunc(workspaceHandler)) |
706 http.HandleFunc("/recent/", autogzip.HandleFunc(recentHandler)) | 845 http.HandleFunc("/recent/", autogzip.HandleFunc(recentHandler)) |
707 http.HandleFunc("/iframe/", autogzip.HandleFunc(iframeHandler)) | 846 http.HandleFunc("/iframe/", autogzip.HandleFunc(iframeHandler)) |
708 http.HandleFunc("/json/", autogzip.HandleFunc(tryInfoHandler)) | 847 http.HandleFunc("/json/", autogzip.HandleFunc(tryInfoHandler)) |
| 848 http.HandleFunc("/sources/", autogzip.HandleFunc(sourcesHandler)) |
709 | 849 |
710 // Resources are served directly | 850 // Resources are served directly |
711 // TODO add support for caching/etags/gzip | 851 // TODO add support for caching/etags/gzip |
712 http.Handle("/res/", autogzip.Handle(http.FileServer(http.Dir("./")))) | 852 http.Handle("/res/", autogzip.Handle(http.FileServer(http.Dir("./")))) |
713 | 853 |
714 // TODO Break out /c/ as it's own handler. | 854 // TODO Break out /c/ as it's own handler. |
715 http.HandleFunc("/", autogzip.HandleFunc(mainHandler)) | 855 http.HandleFunc("/", autogzip.HandleFunc(mainHandler)) |
716 log.Fatal(http.ListenAndServe(*port, nil)) | 856 log.Fatal(http.ListenAndServe(*port, nil)) |
717 } | 857 } |
OLD | NEW |