| 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/binary" |
| 9 "encoding/json" | 9 "encoding/json" |
| 10 "flag" | 10 "flag" |
| 11 "fmt" | 11 "fmt" |
| 12 htemplate "html/template" | 12 htemplate "html/template" |
| 13 "image" | 13 "image" |
| 14 _ "image/gif" | 14 _ "image/gif" |
| 15 _ "image/jpeg" | 15 _ "image/jpeg" |
| 16 "image/png" | 16 "image/png" |
| 17 "io/ioutil" | 17 "io/ioutil" |
| 18 "math/rand" | 18 "math/rand" |
| 19 "net" | 19 "net" |
| 20 "net/http" | 20 "net/http" |
| 21 "os" | 21 "os" |
| 22 "os/exec" | 22 "os/exec" |
| 23 "path/filepath" | 23 "path/filepath" |
| 24 "regexp" | 24 "regexp" |
| 25 "strconv" |
| 25 "strings" | 26 "strings" |
| 26 "text/template" | 27 "text/template" |
| 27 "time" | 28 "time" |
| 28 ) | 29 ) |
| 29 | 30 |
| 30 import ( | 31 import ( |
| 31 "github.com/fiorix/go-web/autogzip" | 32 "github.com/fiorix/go-web/autogzip" |
| 32 _ "github.com/go-sql-driver/mysql" | 33 _ "github.com/go-sql-driver/mysql" |
| 33 "github.com/golang/glog" | 34 "github.com/golang/glog" |
| 34 _ "github.com/mattn/go-sqlite3" | 35 _ "github.com/mattn/go-sqlite3" |
| (...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 79 | 80 |
| 80 // imageLink is the regex that matches URLs paths that are direct links
to PNGs. | 81 // imageLink is the regex that matches URLs paths that are direct links
to PNGs. |
| 81 imageLink = regexp.MustCompile("^/i/([a-z0-9-]+.png)$") | 82 imageLink = regexp.MustCompile("^/i/([a-z0-9-]+.png)$") |
| 82 | 83 |
| 83 // tryInfoLink is the regex that matches URLs paths that are direct link
s to data about a single try. | 84 // tryInfoLink is the regex that matches URLs paths that are direct link
s to data about a single try. |
| 84 tryInfoLink = regexp.MustCompile("^/json/([a-f0-9]+)$") | 85 tryInfoLink = regexp.MustCompile("^/json/([a-f0-9]+)$") |
| 85 | 86 |
| 86 // workspaceLink is the regex that matches URLs paths for workspaces. | 87 // workspaceLink is the regex that matches URLs paths for workspaces. |
| 87 workspaceLink = regexp.MustCompile("^/w/([a-z0-9-]+)$") | 88 workspaceLink = regexp.MustCompile("^/w/([a-z0-9-]+)$") |
| 88 | 89 |
| 90 // errorRE is ther regex that matches compiler errors and extracts the l
ine / column information. |
| 91 errorRE = regexp.MustCompile("^.*.cpp:(\\d+):(\\d+):\\s*(.*)") |
| 92 |
| 89 // workspaceNameAdj is a list of adjectives for building workspace names
. | 93 // workspaceNameAdj is a list of adjectives for building workspace names
. |
| 90 workspaceNameAdj = []string{ | 94 workspaceNameAdj = []string{ |
| 91 "autumn", "hidden", "bitter", "misty", "silent", "empty", "dry",
"dark", | 95 "autumn", "hidden", "bitter", "misty", "silent", "empty", "dry",
"dark", |
| 92 "summer", "icy", "delicate", "quiet", "white", "cool", "spring",
"winter", | 96 "summer", "icy", "delicate", "quiet", "white", "cool", "spring",
"winter", |
| 93 "patient", "twilight", "dawn", "crimson", "wispy", "weathered",
"blue", | 97 "patient", "twilight", "dawn", "crimson", "wispy", "weathered",
"blue", |
| 94 "billowing", "broken", "cold", "damp", "falling", "frosty", "gre
en", | 98 "billowing", "broken", "cold", "damp", "falling", "frosty", "gre
en", |
| 95 "long", "late", "lingering", "bold", "little", "morning", "muddy
", "old", | 99 "long", "late", "lingering", "bold", "little", "morning", "muddy
", "old", |
| 96 "red", "rough", "still", "small", "sparkling", "throbbing", "shy
", | 100 "red", "rough", "still", "small", "sparkling", "throbbing", "shy
", |
| 97 "wandering", "withered", "wild", "black", "young", "holy", "soli
tary", | 101 "wandering", "withered", "wild", "black", "young", "holy", "soli
tary", |
| 98 "fragrant", "aged", "snowy", "proud", "floral", "restless", "div
ine", | 102 "fragrant", "aged", "snowy", "proud", "floral", "restless", "div
ine", |
| (...skipping 299 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 398 return hash, err | 402 return hash, err |
| 399 } | 403 } |
| 400 | 404 |
| 401 // expandGyp produces the GYP file needed to build the code | 405 // expandGyp produces the GYP file needed to build the code |
| 402 func expandGyp(hash string) error { | 406 func expandGyp(hash string) error { |
| 403 return writeTemplate(fmt.Sprintf("../../../cache/%s.gyp", hash), gypTemp
late, struct{ Hash string }{hash}) | 407 return writeTemplate(fmt.Sprintf("../../../cache/%s.gyp", hash), gypTemp
late, struct{ Hash string }{hash}) |
| 404 } | 408 } |
| 405 | 409 |
| 406 // response is serialized to JSON as a response to POSTs. | 410 // response is serialized to JSON as a response to POSTs. |
| 407 type response struct { | 411 type response struct { |
| 408 » Message string `json:"message"` | 412 » Message string `json:"message"` |
| 409 » StdOut string `json:"stdout"` | 413 » CompileErrors []compileError `json:"compileErrors"` |
| 410 » Img string `json:"img"` | 414 » Img string `json:"img"` |
| 411 » Hash string `json:"hash"` | 415 » Hash string `json:"hash"` |
| 412 } | 416 } |
| 413 | 417 |
| 414 // doCmd executes the given command line string; the command being | 418 // doCmd executes the given command line string; the command being |
| 415 // run is expected to not care what its current working directory is. | 419 // run is expected to not care what its current working directory is. |
| 416 // Returns the stdout and stderr. | 420 // Returns the stdout and stderr. |
| 417 func doCmd(commandLine string) (string, error) { | 421 func doCmd(commandLine string) (string, error) { |
| 418 glog.Infof("Command: %q\n", commandLine) | 422 glog.Infof("Command: %q\n", commandLine) |
| 419 programAndArgs := strings.SplitN(commandLine, " ", 2) | 423 programAndArgs := strings.SplitN(commandLine, " ", 2) |
| 420 program := programAndArgs[0] | 424 program := programAndArgs[0] |
| 421 args := []string{} | 425 args := []string{} |
| (...skipping 18 matching lines...) Expand all Loading... |
| 440 } | 444 } |
| 441 | 445 |
| 442 // reportTryError formats an HTTP error response in JSON and also logs the detai
led error message. | 446 // reportTryError formats an HTTP error response in JSON and also logs the detai
led error message. |
| 443 func reportTryError(w http.ResponseWriter, r *http.Request, err error, message,
hash string) { | 447 func reportTryError(w http.ResponseWriter, r *http.Request, err error, message,
hash string) { |
| 444 m := response{ | 448 m := response{ |
| 445 Message: message, | 449 Message: message, |
| 446 Hash: hash, | 450 Hash: hash, |
| 447 } | 451 } |
| 448 glog.Errorf("%s\n%s", message, err) | 452 glog.Errorf("%s\n%s", message, err) |
| 449 resp, err := json.Marshal(m) | 453 resp, err := json.Marshal(m) |
| 454 |
| 450 if err != nil { | 455 if err != nil { |
| 451 http.Error(w, "Failed to serialize a response", 500) | 456 http.Error(w, "Failed to serialize a response", 500) |
| 452 return | 457 return |
| 458 } |
| 459 w.Header().Set("Content-Type", "text/plain") |
| 460 w.Write(resp) |
| 461 } |
| 462 |
| 463 func reportCompileError(w http.ResponseWriter, r *http.Request, compileErrors []
compileError, hash string) { |
| 464 m := response{ |
| 465 CompileErrors: compileErrors, |
| 466 Hash: hash, |
| 467 } |
| 468 |
| 469 resp, err := json.Marshal(m) |
| 470 |
| 471 if err != nil { |
| 472 http.Error(w, "Failed to serialize a response", 500) |
| 473 return |
| 453 } | 474 } |
| 454 w.Header().Set("Content-Type", "text/plain") | 475 w.Header().Set("Content-Type", "text/plain") |
| 455 w.Write(resp) | 476 w.Write(resp) |
| 456 } | 477 } |
| 457 | 478 |
| 458 func writeToDatabase(hash string, code string, workspaceName string, source int,
width, height int, gpu bool) { | 479 func writeToDatabase(hash string, code string, workspaceName string, source int,
width, height int, gpu bool) { |
| 459 if db == nil { | 480 if db == nil { |
| 460 return | 481 return |
| 461 } | 482 } |
| 462 if _, err := db.Exec("INSERT INTO webtry (code, hash, width, height, gpu
, source_image_id) VALUES(?, ?, ?, ?, ?, ?)", code, hash, width, height, gpu, so
urce); err != nil { | 483 if _, err := db.Exec("INSERT INTO webtry (code, hash, width, height, gpu
, source_image_id) VALUES(?, ?, ?, ?, ?, ?)", code, hash, width, height, gpu, so
urce); err != nil { |
| (...skipping 324 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 787 w.Header().Set("Content-Type", "application/json") | 808 w.Header().Set("Content-Type", "application/json") |
| 788 w.Write(resp) | 809 w.Write(resp) |
| 789 } | 810 } |
| 790 | 811 |
| 791 func cleanCompileOutput(s, hash string) string { | 812 func cleanCompileOutput(s, hash string) string { |
| 792 old := "../../../cache/src/" + hash + ".cpp:" | 813 old := "../../../cache/src/" + hash + ".cpp:" |
| 793 glog.Infof("replacing %q\n", old) | 814 glog.Infof("replacing %q\n", old) |
| 794 return strings.Replace(s, old, "usercode.cpp:", -1) | 815 return strings.Replace(s, old, "usercode.cpp:", -1) |
| 795 } | 816 } |
| 796 | 817 |
| 818 type compileError struct { |
| 819 Line int `json:"line"` |
| 820 Column int `json:"column"` |
| 821 Error string `json:"error"` |
| 822 } |
| 823 |
| 797 // mainHandler handles the GET and POST of the main page. | 824 // mainHandler handles the GET and POST of the main page. |
| 798 func mainHandler(w http.ResponseWriter, r *http.Request) { | 825 func mainHandler(w http.ResponseWriter, r *http.Request) { |
| 799 glog.Infof("Main Handler: %q\n", r.URL.Path) | 826 glog.Infof("Main Handler: %q\n", r.URL.Path) |
| 800 requestsCounter.Inc(1) | 827 requestsCounter.Inc(1) |
| 801 if r.Method == "GET" { | 828 if r.Method == "GET" { |
| 802 code := DEFAULT_SAMPLE | 829 code := DEFAULT_SAMPLE |
| 803 source := 0 | 830 source := 0 |
| 804 width := 256 | 831 width := 256 |
| 805 height := 256 | 832 height := 256 |
| 806 gpu := false | 833 gpu := false |
| (...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 862 cmd += " --gpu" | 889 cmd += " --gpu" |
| 863 } | 890 } |
| 864 if *useChroot { | 891 if *useChroot { |
| 865 cmd = "schroot -c webtry --directory=/ -- /skia_build/sk
ia/experimental/webtry/" + cmd | 892 cmd = "schroot -c webtry --directory=/ -- /skia_build/sk
ia/experimental/webtry/" + cmd |
| 866 } | 893 } |
| 867 if request.Source > 0 { | 894 if request.Source > 0 { |
| 868 cmd += fmt.Sprintf(" --source image-%d.png", request.Sou
rce) | 895 cmd += fmt.Sprintf(" --source image-%d.png", request.Sou
rce) |
| 869 } | 896 } |
| 870 | 897 |
| 871 message, err := doCmd(cmd) | 898 message, err := doCmd(cmd) |
| 899 |
| 900 outputLines := strings.Split(message, "\n") |
| 901 errorLines := []compileError{} |
| 902 for _, line := range outputLines { |
| 903 match := errorRE.FindStringSubmatch(line) |
| 904 if len(match) > 0 { |
| 905 lineNumber, parseError := strconv.Atoi(match[1]) |
| 906 if parseError != nil { |
| 907 glog.Errorf("ERROR: Couldn't parse line
number from %s\n", match[1]) |
| 908 continue |
| 909 } |
| 910 columnNumber, parseError := strconv.Atoi(match[2
]) |
| 911 if parseError != nil { |
| 912 glog.Errorf("ERROR: Couldn't parse colum
n number from %s\n", match[2]) |
| 913 continue |
| 914 } |
| 915 errorLines = append(errorLines, |
| 916 compileError{ |
| 917 Line: lineNumber, |
| 918 Column: columnNumber, |
| 919 Error: match[3], |
| 920 }) |
| 921 } |
| 922 } |
| 923 |
| 872 if err != nil { | 924 if err != nil { |
| 873 » » » reportTryError(w, r, err, "Failed to run the code:\n"+me
ssage, hash) | 925 » » » if len(errorLines) > 0 { |
| 926 » » » » reportCompileError(w, r, errorLines, hash) |
| 927 » » » } else { |
| 928 » » » » reportTryError(w, r, err, "Failed to run the cod
e:\n"+message, hash) |
| 929 » » » } |
| 874 return | 930 return |
| 875 } | 931 } |
| 932 |
| 876 png, err := ioutil.ReadFile("../../../inout/" + hash + ".png") | 933 png, err := ioutil.ReadFile("../../../inout/" + hash + ".png") |
| 877 if err != nil { | 934 if err != nil { |
| 878 reportTryError(w, r, err, "Failed to open the generated
PNG.", hash) | 935 reportTryError(w, r, err, "Failed to open the generated
PNG.", hash) |
| 879 return | 936 return |
| 880 } | 937 } |
| 881 | 938 |
| 882 m := response{ | 939 m := response{ |
| 883 Message: message, | 940 Message: message, |
| 884 Img: base64.StdEncoding.EncodeToString([]byte(png)), | 941 Img: base64.StdEncoding.EncodeToString([]byte(png)), |
| 885 Hash: hash, | 942 Hash: hash, |
| (...skipping 18 matching lines...) Expand all Loading... |
| 904 http.HandleFunc("/sources/", autogzip.HandleFunc(sourcesHandler)) | 961 http.HandleFunc("/sources/", autogzip.HandleFunc(sourcesHandler)) |
| 905 | 962 |
| 906 // Resources are served directly | 963 // Resources are served directly |
| 907 // TODO add support for caching/etags/gzip | 964 // TODO add support for caching/etags/gzip |
| 908 http.Handle("/res/", autogzip.Handle(http.FileServer(http.Dir("./")))) | 965 http.Handle("/res/", autogzip.Handle(http.FileServer(http.Dir("./")))) |
| 909 | 966 |
| 910 // TODO Break out /c/ as it's own handler. | 967 // TODO Break out /c/ as it's own handler. |
| 911 http.HandleFunc("/", autogzip.HandleFunc(mainHandler)) | 968 http.HandleFunc("/", autogzip.HandleFunc(mainHandler)) |
| 912 glog.Fatal(http.ListenAndServe(*port, nil)) | 969 glog.Fatal(http.ListenAndServe(*port, nil)) |
| 913 } | 970 } |
| OLD | NEW |