Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(46)

Unified Diff: experimental/webtry/webtry.go

Issue 231853002: Cleaned up the start page, added wait cursor, disabled Run button while waiting for results. (Closed) Base URL: https://skia.googlesource.com/skia.git@master
Patch Set: first pass at workspaces Created 6 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « experimental/webtry/templates/workspace.html ('k') | no next file » | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: experimental/webtry/webtry.go
diff --git a/experimental/webtry/webtry.go b/experimental/webtry/webtry.go
index 1b678ecb778334dcc6757afd34ee6322eb59b202..0e8682a6eef511d4b1a567d3a30a69dd8adfe334 100644
--- a/experimental/webtry/webtry.go
+++ b/experimental/webtry/webtry.go
@@ -13,6 +13,7 @@ import (
htemplate "html/template"
"io/ioutil"
"log"
+ "math/rand"
"net/http"
"os"
"os/exec"
@@ -45,9 +46,12 @@ var (
// indexTemplate is the main index.html page we serve.
indexTemplate *htemplate.Template = nil
- // recentTemplate is a list of recent images.
+ // recentTemplate is a list of recent images.
recentTemplate *htemplate.Template = nil
+ // workspaceTemplate is the page for workspaces, a series of webtrys.
+ workspaceTemplate *htemplate.Template = nil
+
// db is the database, nil if we don't have an SQL database to store data into.
db *sql.DB = nil
@@ -56,6 +60,33 @@ var (
// imageLink is the regex that matches URLs paths that are direct links to PNGs.
imageLink = regexp.MustCompile("^/i/([a-f0-9]+.png)$")
+
+ // workspaceLink is the regex that matches URLs paths for workspaces.
+ workspaceLink = regexp.MustCompile("^/w/([a-z0-9-]+)$")
+
+ workspaceNameAdj = []string{
+ "autumn", "hidden", "bitter", "misty", "silent", "empty", "dry", "dark",
+ "summer", "icy", "delicate", "quiet", "white", "cool", "spring", "winter",
+ "patient", "twilight", "dawn", "crimson", "wispy", "weathered", "blue",
+ "billowing", "broken", "cold", "damp", "falling", "frosty", "green",
+ "long", "late", "lingering", "bold", "little", "morning", "muddy", "old",
+ "red", "rough", "still", "small", "sparkling", "throbbing", "shy",
+ "wandering", "withered", "wild", "black", "young", "holy", "solitary",
+ "fragrant", "aged", "snowy", "proud", "floral", "restless", "divine",
+ "polished", "ancient", "purple", "lively", "nameless",
+ }
+
+ workspaceNameNoun = []string{
+ "waterfall", "river", "breeze", "moon", "rain", "wind", "sea", "morning",
+ "snow", "lake", "sunset", "pine", "shadow", "leaf", "dawn", "glitter",
+ "forest", "hill", "cloud", "meadow", "sun", "glade", "bird", "brook",
+ "butterfly", "bush", "dew", "dust", "field", "fire", "flower", "firefly",
+ "feather", "grass", "haze", "mountain", "night", "pond", "darkness",
+ "snowflake", "silence", "sound", "sky", "shape", "surf", "thunder",
+ "violet", "water", "wildflower", "wave", "water", "resonance", "sun",
+ "wood", "dream", "cherry", "tree", "fog", "frost", "voice", "paper",
+ "frog", "smoke", "star",
+ }
)
// flags
@@ -100,6 +131,11 @@ func init() {
panic(err)
}
+ workspaceTemplate, err = htemplate.ParseFiles(filepath.Join(cwd, "templates/workspace.html"))
+ if err != nil {
+ panic(err)
+ }
+
// Connect to MySQL server. First, get the password from the metadata server.
// See https://developers.google.com/compute/docs/metadata#custom.
req, err := http.NewRequest("GET", "http://metadata/computeMetadata/v1/instance/attributes/password", nil)
@@ -123,6 +159,7 @@ func init() {
panic(err)
}
} else {
+ log.Printf("INFO: Failed to find metadata, unable to connect to MySQL server (Expected when running locally): %q\n", err)
// Fallback to sqlite for local use.
db, err = sql.Open("sqlite3", "./webtry.db")
if err != nil {
@@ -135,8 +172,24 @@ func init() {
hash CHAR(64) DEFAULT '' NOT NULL,
PRIMARY KEY(hash)
)`
- db.Exec(sql)
- log.Printf("INFO: Failed to find metadata, unable to connect to MySQL server (Expected when running locally): %q\n", err)
+ _, err = db.Exec(sql)
+ log.Printf("Info: status creating sqlite table for webtry: %q\n", err)
+ sql = `CREATE TABLE workspace (
+ name TEXT DEFAULT '' NOT NULL,
+ create_ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ PRIMARY KEY(name)
+ )`
+ _, err = db.Exec(sql)
+ log.Printf("Info: status creating sqlite table for workspace: %q\n", err)
+ sql = `CREATE TABLE workspacetry (
+ name TEXT DEFAULT '' NOT NULL,
+ create_ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
+ hash CHAR(64) DEFAULT '' NOT NULL,
+ hidden INTEGER DEFAULT 0 NOT NULL,
+ FOREIGN KEY (name) REFERENCES workspace(name)
+ )`
+ _, err = db.Exec(sql)
+ log.Printf("Info: status creating sqlite table for workspace try: %q\n", err)
}
}
@@ -231,13 +284,18 @@ func reportError(w http.ResponseWriter, r *http.Request, err error, message stri
w.Write(resp)
}
-func writeToDatabase(hash string, code string) {
+func writeToDatabase(hash string, code string, workspaceName string) {
if db == nil {
return
}
if _, err := db.Exec("INSERT INTO webtry (code, hash) VALUES(?, ?)", code, hash); err != nil {
log.Printf("ERROR: Failed to insert code into database: %q\n", err)
}
+ if workspaceName != "" {
+ if _, err := db.Exec("INSERT INTO workspacetry (name, hash) VALUES(?, ?)", workspaceName, hash); err != nil {
+ log.Printf("ERROR: Failed to insert into workspacetry table: %q\n", err)
+ }
+ }
}
func cssHandler(w http.ResponseWriter, r *http.Request) {
@@ -294,6 +352,99 @@ func recentHandler(w http.ResponseWriter, r *http.Request) {
}
}
+type Workspace struct {
+ Name string
+ Code string
+ Tries []Try
+}
+
+func newWorkspace() (string, error) {
+ for i := 0; i < 10; i++ {
+ adj := workspaceNameAdj[rand.Intn(len(workspaceNameAdj))]
+ noun := workspaceNameNoun[rand.Intn(len(workspaceNameNoun))]
+ suffix := rand.Intn(1000)
+ name := fmt.Sprintf("%s-%s-%d", adj, noun, suffix)
+ if _, err := db.Exec("INSERT INTO workspace (name) VALUES(?)", name); err == nil {
+ return name, nil
+ } else {
+ log.Printf("ERROR: Failed to insert workspace into database: %q\n", err)
+ }
+ }
+ return "", fmt.Errorf("Failed to create a new workspace")
+}
+
+func getCode(hash string) string {
+ code := ""
+ if err := db.QueryRow("SELECT code FROM webtry WHERE hash=?", hash).Scan(&code); err != nil {
+ log.Printf("ERROR: Code for hash is missing: %q\n", err)
+ }
+ return code
+}
+
+func workspaceHandler(w http.ResponseWriter, r *http.Request) {
+ // POST creates a new workspace and redirect to the newly created workspace.
+ // GET w/o a namespace name just gets the 'create' page.
+ // GET w/name gets that workspace doc, which is just a list of tries.
+ // What's the difference? The 'create' flow has a 'Create' button
+ // and no code. A workspace has a 'Run' button
+ // and shows the code for the most recent try. Also includes a list
+ // of all the tries in this workspace with a fingernail for each one.
+ // Run is actually handled by a POST to /.
+ log.Printf("Workspace Handler: %q\n", r.URL.Path)
+ if r.Method == "GET" {
+ tries := []Try{}
+ match := workspaceLink.FindStringSubmatch(r.URL.Path)
+ name := ""
+ if len(match) == 2 {
+ log.Printf("Got a valid match %q\n", match)
+ name = match[1]
+ // Load all the tries for this workspace.
+ rows, err := db.Query("SELECT create_ts, hash FROM workspacetry WHERE name=? ORDER BY create_ts DESC ", name)
+ if err != nil {
+ reportError(w, r, err, "Failed to select.")
+ return
+ }
+ for rows.Next() {
+ var hash string
+ var create_ts time.Time
+ if err := rows.Scan(&create_ts, &hash); err != nil {
+ log.Printf("Error: failed to fetch from database: %q", err)
+ continue
+ }
+ tries = append(tries, Try{Hash: hash, CreateTS: create_ts.Format("2006-02-01")})
+ }
+ }
+ var code string
+ if len(tries) == 0 {
+ code = DEFAULT_SAMPLE
+ } else {
+ code = getCode(tries[len(tries)-1].Hash)
+ }
+ // If len(tries) == 0 then return the 'default code', otherwise include the most recent code.
+ // We don't worry about existence of a workspace with this name since trying to
+ // add a new try will fail.
+
+ if err := workspaceTemplate.Execute(w, Workspace{Tries: tries, Code: code, Name: name}); err != nil {
+ log.Printf("ERROR: Failed to expand template: %q\n", err)
+ }
+ // Update POST to / so that it takes a JSON document that includes not only
+ // the code but an optional 'name' parameter. If the name is present the
+ // POST handler should also add a row to the workspacetry table.
+ } else if r.Method == "POST" {
+ // Create a name and try to insert it into the db.
+ // Redirect to /w/<name>
+ if _, err := db.Exec("INSERT INTO workspace (name) VALUES(?)", "autumn-breeze-123"); err != nil {
+ log.Printf("ERROR: Failed to insert workspace into database: %q\n", err)
+ }
+ name, err := newWorkspace()
+ if err != nil {
+ http.Error(w, "Failed to create a new workspace.", 500)
+ return
+ }
+ http.Redirect(w, r, "/w/"+name, 302)
+ }
+}
+
// hasPreProcessor returns true if any line in the code begins with a # char.
func hasPreProcessor(code string) bool {
lines := strings.Split(code, "\n")
@@ -305,6 +456,11 @@ func hasPreProcessor(code string) bool {
return false
}
+type TryRequest struct {
+ Code string `json:"code"`
+ Name string `json:"name"`
+}
+
// mainHandler handles the GET and POST of the main page.
func mainHandler(w http.ResponseWriter, r *http.Request) {
log.Printf("Main Handler: %q\n", r.URL.Path)
@@ -340,18 +496,22 @@ func mainHandler(w http.ResponseWriter, r *http.Request) {
reportError(w, r, err, "Code too large.")
return
}
- code := string(buf.Bytes())
- if hasPreProcessor(code) {
+ request := TryRequest{}
+ if err := json.Unmarshal(buf.Bytes(), &request); err != nil {
+ reportError(w, r, err, "Coulnd't decode JSON.")
+ return
+ }
+ if hasPreProcessor(request.Code) {
err := fmt.Errorf("Found preprocessor macro in code.")
reportError(w, r, err, "Preprocessor macros aren't allowed.")
return
}
- hash, err := expandCode(LineNumbers(code))
+ hash, err := expandCode(LineNumbers(request.Code))
if err != nil {
reportError(w, r, err, "Failed to write the code to compile.")
return
}
- writeToDatabase(hash, code)
+ writeToDatabase(hash, request.Code, request.Name)
message, err := doCmd(fmt.Sprintf(RESULT_COMPILE, hash, hash), true)
if err != nil {
reportError(w, r, err, "Failed to compile the code:\n"+message)
@@ -403,6 +563,7 @@ func mainHandler(w http.ResponseWriter, r *http.Request) {
func main() {
flag.Parse()
http.HandleFunc("/i/", imageHandler)
+ http.HandleFunc("/w/", workspaceHandler)
http.HandleFunc("/recent/", recentHandler)
http.HandleFunc("/css/", cssHandler)
http.HandleFunc("/", mainHandler)
« no previous file with comments | « experimental/webtry/templates/workspace.html ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698