| 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 |