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) | |
mtklein
2014/04/22 17:24:18
Might as well handle this error? Even if this is
jcgregorio
2014/04/22 18:02:08
In case of error getCode returns the empty string,
| |
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 |