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/json" | 8 "encoding/json" |
9 "flag" | 9 "flag" |
10 "fmt" | 10 "fmt" |
(...skipping 49 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
60 | 60 |
61 // directLink is the regex that matches URLs paths that are direct links
. | 61 // directLink is the regex that matches URLs paths that are direct links
. |
62 directLink = regexp.MustCompile("^/c/([a-f0-9]+)$") | 62 directLink = regexp.MustCompile("^/c/([a-f0-9]+)$") |
63 | 63 |
64 // iframeLink is the regex that matches URLs paths that are links to ifr
ames. | 64 // iframeLink is the regex that matches URLs paths that are links to ifr
ames. |
65 iframeLink = regexp.MustCompile("^/iframe/([a-f0-9]+)$") | 65 iframeLink = regexp.MustCompile("^/iframe/([a-f0-9]+)$") |
66 | 66 |
67 // imageLink is the regex that matches URLs paths that are direct links
to PNGs. | 67 // imageLink is the regex that matches URLs paths that are direct links
to PNGs. |
68 imageLink = regexp.MustCompile("^/i/([a-f0-9]+.png)$") | 68 imageLink = regexp.MustCompile("^/i/([a-f0-9]+.png)$") |
69 | 69 |
| 70 // tryInfoLink is the regex that matches URLs paths that are direct link
s to data about a single try. |
| 71 tryInfoLink = regexp.MustCompile("^/json/([a-f0-9]+)$") |
| 72 |
70 // workspaceLink is the regex that matches URLs paths for workspaces. | 73 // workspaceLink is the regex that matches URLs paths for workspaces. |
71 workspaceLink = regexp.MustCompile("^/w/([a-z0-9-]+)$") | 74 workspaceLink = regexp.MustCompile("^/w/([a-z0-9-]+)$") |
72 | 75 |
73 // workspaceNameAdj is a list of adjectives for building workspace names
. | 76 // workspaceNameAdj is a list of adjectives for building workspace names
. |
74 workspaceNameAdj = []string{ | 77 workspaceNameAdj = []string{ |
75 "autumn", "hidden", "bitter", "misty", "silent", "empty", "dry",
"dark", | 78 "autumn", "hidden", "bitter", "misty", "silent", "empty", "dry",
"dark", |
76 "summer", "icy", "delicate", "quiet", "white", "cool", "spring",
"winter", | 79 "summer", "icy", "delicate", "quiet", "white", "cool", "spring",
"winter", |
77 "patient", "twilight", "dawn", "crimson", "wispy", "weathered",
"blue", | 80 "patient", "twilight", "dawn", "crimson", "wispy", "weathered",
"blue", |
78 "billowing", "broken", "cold", "damp", "falling", "frosty", "gre
en", | 81 "billowing", "broken", "cold", "damp", "falling", "frosty", "gre
en", |
79 "long", "late", "lingering", "bold", "little", "morning", "muddy
", "old", | 82 "long", "late", "lingering", "bold", "little", "morning", "muddy
", "old", |
(...skipping 295 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
375 recent = append(recent, Try{Hash: hash, CreateTS: create_ts.Form
at("2006-02-01")}) | 378 recent = append(recent, Try{Hash: hash, CreateTS: create_ts.Form
at("2006-02-01")}) |
376 } | 379 } |
377 if err := recentTemplate.Execute(w, Recent{Tries: recent}); err != nil { | 380 if err := recentTemplate.Execute(w, Recent{Tries: recent}); err != nil { |
378 log.Printf("ERROR: Failed to expand template: %q\n", err) | 381 log.Printf("ERROR: Failed to expand template: %q\n", err) |
379 } | 382 } |
380 } | 383 } |
381 | 384 |
382 type Workspace struct { | 385 type Workspace struct { |
383 Name string | 386 Name string |
384 Code string | 387 Code string |
| 388 Hash string |
385 Tries []Try | 389 Tries []Try |
386 } | 390 } |
387 | 391 |
388 // newWorkspace generates a new random workspace name and stores it in the datab
ase. | 392 // newWorkspace generates a new random workspace name and stores it in the datab
ase. |
389 func newWorkspace() (string, error) { | 393 func newWorkspace() (string, error) { |
390 for i := 0; i < 10; i++ { | 394 for i := 0; i < 10; i++ { |
391 adj := workspaceNameAdj[rand.Intn(len(workspaceNameAdj))] | 395 adj := workspaceNameAdj[rand.Intn(len(workspaceNameAdj))] |
392 noun := workspaceNameNoun[rand.Intn(len(workspaceNameNoun))] | 396 noun := workspaceNameNoun[rand.Intn(len(workspaceNameNoun))] |
393 suffix := rand.Intn(1000) | 397 suffix := rand.Intn(1000) |
394 name := fmt.Sprintf("%s-%s-%d", adj, noun, suffix) | 398 name := fmt.Sprintf("%s-%s-%d", adj, noun, suffix) |
395 if _, err := db.Exec("INSERT INTO workspace (name) VALUES(?)", n
ame); err == nil { | 399 if _, err := db.Exec("INSERT INTO workspace (name) VALUES(?)", n
ame); err == nil { |
396 return name, nil | 400 return name, nil |
397 } else { | 401 } else { |
398 log.Printf("ERROR: Failed to insert workspace into datab
ase: %q\n", err) | 402 log.Printf("ERROR: Failed to insert workspace into datab
ase: %q\n", err) |
399 } | 403 } |
400 } | 404 } |
401 return "", fmt.Errorf("Failed to create a new workspace") | 405 return "", fmt.Errorf("Failed to create a new workspace") |
402 } | 406 } |
403 | 407 |
404 // getCode returns the code for a given hash, or the empty string if not found. | 408 // getCode returns the code for a given hash, or the empty string if not found. |
405 func getCode(hash string) string { | 409 func getCode(hash string) (string, error) { |
406 code := "" | 410 code := "" |
407 if err := db.QueryRow("SELECT code FROM webtry WHERE hash=?", hash).Scan
(&code); err != nil { | 411 if err := db.QueryRow("SELECT code FROM webtry WHERE hash=?", hash).Scan
(&code); err != nil { |
408 log.Printf("ERROR: Code for hash is missing: %q\n", err) | 412 log.Printf("ERROR: Code for hash is missing: %q\n", err) |
| 413 return code, err |
409 } | 414 } |
410 » return code | 415 » return code, nil |
411 } | 416 } |
412 | 417 |
413 func workspaceHandler(w http.ResponseWriter, r *http.Request) { | 418 func workspaceHandler(w http.ResponseWriter, r *http.Request) { |
414 log.Printf("Workspace Handler: %q\n", r.URL.Path) | 419 log.Printf("Workspace Handler: %q\n", r.URL.Path) |
415 if r.Method == "GET" { | 420 if r.Method == "GET" { |
416 tries := []Try{} | 421 tries := []Try{} |
417 match := workspaceLink.FindStringSubmatch(r.URL.Path) | 422 match := workspaceLink.FindStringSubmatch(r.URL.Path) |
418 name := "" | 423 name := "" |
419 if len(match) == 2 { | 424 if len(match) == 2 { |
420 name = match[1] | 425 name = match[1] |
421 rows, err := db.Query("SELECT create_ts, hash FROM works
pacetry WHERE name=? ORDER BY create_ts", name) | 426 rows, err := db.Query("SELECT create_ts, hash FROM works
pacetry WHERE name=? ORDER BY create_ts", name) |
422 if err != nil { | 427 if err != nil { |
423 reportError(w, r, err, "Failed to select.") | 428 reportError(w, r, err, "Failed to select.") |
424 return | 429 return |
425 } | 430 } |
426 for rows.Next() { | 431 for rows.Next() { |
427 var hash string | 432 var hash string |
428 var create_ts time.Time | 433 var create_ts time.Time |
429 if err := rows.Scan(&create_ts, &hash); err != n
il { | 434 if err := rows.Scan(&create_ts, &hash); err != n
il { |
430 log.Printf("Error: failed to fetch from
database: %q", err) | 435 log.Printf("Error: failed to fetch from
database: %q", err) |
431 continue | 436 continue |
432 } | 437 } |
433 tries = append(tries, Try{Hash: hash, CreateTS:
create_ts.Format("2006-02-01")}) | 438 tries = append(tries, Try{Hash: hash, CreateTS:
create_ts.Format("2006-02-01")}) |
434 } | 439 } |
435 } | 440 } |
436 var code string | 441 var code string |
| 442 var hash string |
437 if len(tries) == 0 { | 443 if len(tries) == 0 { |
438 code = DEFAULT_SAMPLE | 444 code = DEFAULT_SAMPLE |
439 } else { | 445 } else { |
440 » » » code = getCode(tries[len(tries)-1].Hash) | 446 » » » hash = tries[len(tries)-1].Hash |
| 447 » » » code, _ = getCode(hash) |
441 } | 448 } |
442 » » if err := workspaceTemplate.Execute(w, Workspace{Tries: tries, C
ode: code, Name: name}); err != nil { | 449 » » if err := workspaceTemplate.Execute(w, Workspace{Tries: tries, C
ode: code, Name: name, Hash: hash}); err != nil { |
443 log.Printf("ERROR: Failed to expand template: %q\n", err
) | 450 log.Printf("ERROR: Failed to expand template: %q\n", err
) |
444 } | 451 } |
445 } else if r.Method == "POST" { | 452 } else if r.Method == "POST" { |
446 name, err := newWorkspace() | 453 name, err := newWorkspace() |
447 if err != nil { | 454 if err != nil { |
448 http.Error(w, "Failed to create a new workspace.", 500) | 455 http.Error(w, "Failed to create a new workspace.", 500) |
449 return | 456 return |
450 } | 457 } |
451 http.Redirect(w, r, "/w/"+name, 302) | 458 http.Redirect(w, r, "/w/"+name, 302) |
452 } | 459 } |
453 } | 460 } |
454 | 461 |
455 // hasPreProcessor returns true if any line in the code begins with a # char. | 462 // hasPreProcessor returns true if any line in the code begins with a # char. |
456 func hasPreProcessor(code string) bool { | 463 func hasPreProcessor(code string) bool { |
457 lines := strings.Split(code, "\n") | 464 lines := strings.Split(code, "\n") |
458 for _, s := range lines { | 465 for _, s := range lines { |
459 if strings.HasPrefix(strings.TrimSpace(s), "#") { | 466 if strings.HasPrefix(strings.TrimSpace(s), "#") { |
460 return true | 467 return true |
461 } | 468 } |
462 } | 469 } |
463 return false | 470 return false |
464 } | 471 } |
465 | 472 |
466 type TryRequest struct { | 473 type TryRequest struct { |
467 Code string `json:"code"` | 474 Code string `json:"code"` |
468 » Name string `json:"name"` | 475 » Name string `json:"name"` // Optional name of the workspace the code is
in. |
469 } | 476 } |
470 | 477 |
471 // iframeHandler handles the GET and POST of the main page. | 478 // iframeHandler handles the GET and POST of the main page. |
472 func iframeHandler(w http.ResponseWriter, r *http.Request) { | 479 func iframeHandler(w http.ResponseWriter, r *http.Request) { |
473 log.Printf("IFrame Handler: %q\n", r.URL.Path) | 480 log.Printf("IFrame Handler: %q\n", r.URL.Path) |
474 if r.Method != "GET" { | 481 if r.Method != "GET" { |
475 http.NotFound(w, r) | 482 http.NotFound(w, r) |
476 return | 483 return |
477 } | 484 } |
478 match := iframeLink.FindStringSubmatch(r.URL.Path) | 485 match := iframeLink.FindStringSubmatch(r.URL.Path) |
479 if len(match) != 2 { | 486 if len(match) != 2 { |
480 http.NotFound(w, r) | 487 http.NotFound(w, r) |
481 return | 488 return |
482 } | 489 } |
483 hash := match[1] | 490 hash := match[1] |
484 if db == nil { | 491 if db == nil { |
485 http.NotFound(w, r) | 492 http.NotFound(w, r) |
486 return | 493 return |
487 } | 494 } |
488 var code string | 495 var code string |
489 » // Load 'code' with the code found in the database. | 496 » code, err := getCode(hash) |
490 » if err := db.QueryRow("SELECT code FROM webtry WHERE hash=?", hash).Scan
(&code); err != nil { | 497 » if err != nil { |
491 http.NotFound(w, r) | 498 http.NotFound(w, r) |
492 return | 499 return |
493 } | 500 } |
494 // Expand the template. | 501 // Expand the template. |
495 if err := iframeTemplate.Execute(w, userCode{UserCode: code, Hash: hash}
); err != nil { | 502 if err := iframeTemplate.Execute(w, userCode{UserCode: code, Hash: hash}
); err != nil { |
496 log.Printf("ERROR: Failed to expand template: %q\n", err) | 503 log.Printf("ERROR: Failed to expand template: %q\n", err) |
497 } | 504 } |
498 } | 505 } |
499 | 506 |
| 507 type TryInfo struct { |
| 508 Hash string `json:"hash"` |
| 509 Code string `json:"code"` |
| 510 } |
| 511 |
| 512 // tryInfoHandler returns information about a specific try. |
| 513 func tryInfoHandler(w http.ResponseWriter, r *http.Request) { |
| 514 log.Printf("Try Info Handler: %q\n", r.URL.Path) |
| 515 if r.Method != "GET" { |
| 516 http.NotFound(w, r) |
| 517 return |
| 518 } |
| 519 match := tryInfoLink.FindStringSubmatch(r.URL.Path) |
| 520 if len(match) != 2 { |
| 521 http.NotFound(w, r) |
| 522 return |
| 523 } |
| 524 hash := match[1] |
| 525 code, err := getCode(hash) |
| 526 if err != nil { |
| 527 http.NotFound(w, r) |
| 528 return |
| 529 } |
| 530 m := TryInfo{ |
| 531 Hash: hash, |
| 532 Code: code, |
| 533 } |
| 534 resp, err := json.Marshal(m) |
| 535 if err != nil { |
| 536 reportError(w, r, err, "Failed to serialize a response.") |
| 537 return |
| 538 } |
| 539 w.Header().Set("Content-Type", "application/json") |
| 540 w.Write(resp) |
| 541 } |
| 542 |
500 // mainHandler handles the GET and POST of the main page. | 543 // mainHandler handles the GET and POST of the main page. |
501 func mainHandler(w http.ResponseWriter, r *http.Request) { | 544 func mainHandler(w http.ResponseWriter, r *http.Request) { |
502 log.Printf("Main Handler: %q\n", r.URL.Path) | 545 log.Printf("Main Handler: %q\n", r.URL.Path) |
503 if r.Method == "GET" { | 546 if r.Method == "GET" { |
504 code := DEFAULT_SAMPLE | 547 code := DEFAULT_SAMPLE |
505 match := directLink.FindStringSubmatch(r.URL.Path) | 548 match := directLink.FindStringSubmatch(r.URL.Path) |
| 549 var hash string |
506 if len(match) == 2 && r.URL.Path != "/" { | 550 if len(match) == 2 && r.URL.Path != "/" { |
507 » » » hash := match[1] | 551 » » » hash = match[1] |
508 if db == nil { | 552 if db == nil { |
509 http.NotFound(w, r) | 553 http.NotFound(w, r) |
510 return | 554 return |
511 } | 555 } |
512 // Update 'code' with the code found in the database. | 556 // Update 'code' with the code found in the database. |
513 if err := db.QueryRow("SELECT code FROM webtry WHERE has
h=?", hash).Scan(&code); err != nil { | 557 if err := db.QueryRow("SELECT code FROM webtry WHERE has
h=?", hash).Scan(&code); err != nil { |
514 http.NotFound(w, r) | 558 http.NotFound(w, r) |
515 return | 559 return |
516 } | 560 } |
517 } | 561 } |
518 // Expand the template. | 562 // Expand the template. |
519 » » if err := indexTemplate.Execute(w, userCode{UserCode: code}); er
r != nil { | 563 » » if err := indexTemplate.Execute(w, userCode{UserCode: code, Hash
: hash}); err != nil { |
520 log.Printf("ERROR: Failed to expand template: %q\n", err
) | 564 log.Printf("ERROR: Failed to expand template: %q\n", err
) |
521 } | 565 } |
522 } else if r.Method == "POST" { | 566 } else if r.Method == "POST" { |
523 w.Header().Set("Content-Type", "application/json") | 567 w.Header().Set("Content-Type", "application/json") |
524 buf := bytes.NewBuffer(make([]byte, 0, MAX_TRY_SIZE)) | 568 buf := bytes.NewBuffer(make([]byte, 0, MAX_TRY_SIZE)) |
525 n, err := buf.ReadFrom(r.Body) | 569 n, err := buf.ReadFrom(r.Body) |
526 if err != nil { | 570 if err != nil { |
527 reportError(w, r, err, "Failed to read a request body.") | 571 reportError(w, r, err, "Failed to read a request body.") |
528 return | 572 return |
529 } | 573 } |
(...skipping 65 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
595 w.Write(resp) | 639 w.Write(resp) |
596 } | 640 } |
597 } | 641 } |
598 | 642 |
599 func main() { | 643 func main() { |
600 flag.Parse() | 644 flag.Parse() |
601 http.HandleFunc("/i/", imageHandler) | 645 http.HandleFunc("/i/", imageHandler) |
602 http.HandleFunc("/w/", workspaceHandler) | 646 http.HandleFunc("/w/", workspaceHandler) |
603 http.HandleFunc("/recent/", recentHandler) | 647 http.HandleFunc("/recent/", recentHandler) |
604 http.HandleFunc("/iframe/", iframeHandler) | 648 http.HandleFunc("/iframe/", iframeHandler) |
| 649 http.HandleFunc("/json/", tryInfoHandler) |
605 http.HandleFunc("/css/", cssHandler) | 650 http.HandleFunc("/css/", cssHandler) |
606 http.HandleFunc("/js/", jsHandler) | 651 http.HandleFunc("/js/", jsHandler) |
| 652 // TODO Break out /c/ as it's own handler. |
607 http.HandleFunc("/", mainHandler) | 653 http.HandleFunc("/", mainHandler) |
608 log.Fatal(http.ListenAndServe(*port, nil)) | 654 log.Fatal(http.ListenAndServe(*port, nil)) |
609 } | 655 } |
OLD | NEW |