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 |