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

Side by Side Diff: experimental/webtry/webtry.go

Issue 294903017: Add the ability to select a source image to use in the code. (Closed) Base URL: https://skia.googlesource.com/skia.git@master
Patch Set: Created 6 years, 6 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 unified diff | Download patch
OLDNEW
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/json" 9 "encoding/json"
9 "flag" 10 "flag"
10 "fmt" 11 "fmt"
11 htemplate "html/template" 12 htemplate "html/template"
13 "image"
14 _ "image/gif"
15 _ "image/jpeg"
16 "image/png"
12 "io/ioutil" 17 "io/ioutil"
13 "log" 18 "log"
14 "math/rand" 19 "math/rand"
15 "net/http" 20 "net/http"
16 "os" 21 "os"
17 "os/exec" 22 "os/exec"
18 "path/filepath" 23 "path/filepath"
19 "regexp" 24 "regexp"
20 "strings" 25 "strings"
21 "text/template" 26 "text/template"
(...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after
63 // db is the database, nil if we don't have an SQL database to store dat a into. 68 // db is the database, nil if we don't have an SQL database to store dat a into.
64 db *sql.DB = nil 69 db *sql.DB = nil
65 70
66 // directLink is the regex that matches URLs paths that are direct links . 71 // directLink is the regex that matches URLs paths that are direct links .
67 directLink = regexp.MustCompile("^/c/([a-f0-9]+)$") 72 directLink = regexp.MustCompile("^/c/([a-f0-9]+)$")
68 73
69 // iframeLink is the regex that matches URLs paths that are links to ifr ames. 74 // iframeLink is the regex that matches URLs paths that are links to ifr ames.
70 iframeLink = regexp.MustCompile("^/iframe/([a-f0-9]+)$") 75 iframeLink = regexp.MustCompile("^/iframe/([a-f0-9]+)$")
71 76
72 // imageLink is the regex that matches URLs paths that are direct links to PNGs. 77 // imageLink is the regex that matches URLs paths that are direct links to PNGs.
73 » imageLink = regexp.MustCompile("^/i/([a-f0-9]+.png)$") 78 » imageLink = regexp.MustCompile("^/i/([a-z0-9-]+.png)$")
74 79
75 // tryInfoLink is the regex that matches URLs paths that are direct link s to data about a single try. 80 // tryInfoLink is the regex that matches URLs paths that are direct link s to data about a single try.
76 tryInfoLink = regexp.MustCompile("^/json/([a-f0-9]+)$") 81 tryInfoLink = regexp.MustCompile("^/json/([a-f0-9]+)$")
77 82
78 // workspaceLink is the regex that matches URLs paths for workspaces. 83 // workspaceLink is the regex that matches URLs paths for workspaces.
79 workspaceLink = regexp.MustCompile("^/w/([a-z0-9-]+)$") 84 workspaceLink = regexp.MustCompile("^/w/([a-z0-9-]+)$")
80 85
81 // workspaceNameAdj is a list of adjectives for building workspace names . 86 // workspaceNameAdj is a list of adjectives for building workspace names .
82 workspaceNameAdj = []string{ 87 workspaceNameAdj = []string{
83 "autumn", "hidden", "bitter", "misty", "silent", "empty", "dry", "dark", 88 "autumn", "hidden", "bitter", "misty", "silent", "empty", "dry", "dark",
(...skipping 130 matching lines...) Expand 10 before | Expand all | Expand 10 after
214 panic(err) 219 panic(err)
215 } 220 }
216 } else { 221 } else {
217 log.Printf("INFO: Failed to find metadata, unable to connect to MySQL server (Expected when running locally): %q\n", err) 222 log.Printf("INFO: Failed to find metadata, unable to connect to MySQL server (Expected when running locally): %q\n", err)
218 // Fallback to sqlite for local use. 223 // Fallback to sqlite for local use.
219 db, err = sql.Open("sqlite3", "./webtry.db") 224 db, err = sql.Open("sqlite3", "./webtry.db")
220 if err != nil { 225 if err != nil {
221 log.Printf("ERROR: Failed to open: %q\n", err) 226 log.Printf("ERROR: Failed to open: %q\n", err)
222 panic(err) 227 panic(err)
223 } 228 }
224 » » sql := `CREATE TABLE webtry ( 229 » » sql := `CREATE TABLE sources (
230 id INTEGER PRIMARY KEY NOT NULL,
mtklein 2014/05/28 17:36:50 Is it just Rietveld, or is this weirdly indented?
jcgregorio 2014/05/28 18:42:59 Done.
231 image MEDIUMBLOB DEFAULT '' NOT NULL,
232 width INTEGER DEFAULT 0 NOT NULL,
233 height INTEGER DEFAULT 0 NOT NULL,
234 create_ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
235 hidden INTEGER DEFAULT 0 NOT NULL
236 )`
237 » » _, err = db.Exec(sql)
238 » » log.Printf("Info: status creating sqlite table for sources: %q\n ", err)
239
240 » » sql = `CREATE TABLE webtry (
225 code TEXT DEFAULT '' NOT NULL, 241 code TEXT DEFAULT '' NOT NULL,
226 create_ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, 242 create_ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
227 hash CHAR(64) DEFAULT '' NOT NULL, 243 hash CHAR(64) DEFAULT '' NOT NULL,
244 source INTEGER DEFAULT 0 NOT NULL,
245
228 PRIMARY KEY(hash) 246 PRIMARY KEY(hash)
229 )` 247 )`
230 _, err = db.Exec(sql) 248 _, err = db.Exec(sql)
231 log.Printf("Info: status creating sqlite table for webtry: %q\n" , err) 249 log.Printf("Info: status creating sqlite table for webtry: %q\n" , err)
250
232 sql = `CREATE TABLE workspace ( 251 sql = `CREATE TABLE workspace (
233 name CHAR(64) DEFAULT '' NOT NULL, 252 name CHAR(64) DEFAULT '' NOT NULL,
234 create_ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, 253 create_ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
235 PRIMARY KEY(name) 254 PRIMARY KEY(name)
236 )` 255 )`
237 _, err = db.Exec(sql) 256 _, err = db.Exec(sql)
238 log.Printf("Info: status creating sqlite table for workspace: %q \n", err) 257 log.Printf("Info: status creating sqlite table for workspace: %q \n", err)
258
239 sql = `CREATE TABLE workspacetry ( 259 sql = `CREATE TABLE workspacetry (
240 name CHAR(64) DEFAULT '' NOT NULL, 260 name CHAR(64) DEFAULT '' NOT NULL,
241 create_ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, 261 create_ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
242 hash CHAR(64) DEFAULT '' NOT NULL, 262 hash CHAR(64) DEFAULT '' NOT NULL,
243 hidden INTEGER DEFAULT 0 NOT NULL, 263 hidden INTEGER DEFAULT 0 NOT NULL,
264 source INTEGER DEFAULT 0 NOT NULL,
244 265
245 FOREIGN KEY (name) REFERENCES workspace(name) 266 FOREIGN KEY (name) REFERENCES workspace(name)
246 )` 267 )`
247 _, err = db.Exec(sql) 268 _, err = db.Exec(sql)
248 log.Printf("Info: status creating sqlite table for workspace try : %q\n", err) 269 log.Printf("Info: status creating sqlite table for workspace try : %q\n", err)
249 } 270 }
250 271
251 // Ping the database to keep the connection fresh. 272 // Ping the database to keep the connection fresh.
252 go func() { 273 go func() {
253 c := time.Tick(1 * time.Minute) 274 c := time.Tick(1 * time.Minute)
254 for _ = range c { 275 for _ = range c {
255 if err := db.Ping(); err != nil { 276 if err := db.Ping(); err != nil {
256 log.Printf("ERROR: Database failed to respond: % q\n", err) 277 log.Printf("ERROR: Database failed to respond: % q\n", err)
257 } 278 }
258 } 279 }
259 }() 280 }()
260 281
282 writeOutAllSourceImages()
283 }
284
285 func writeOutAllSourceImages() {
286 // Pull all the source images from the db and write them out to inout.
287 //rows, err := db.Query("SELECT id, image, create_ts FROM sources WHERE hidden=0 ORDER BY create_ts DESC")
288 rows, err := db.Query("SELECT id, image, create_ts FROM sources ORDER BY create_ts DESC")
289
290 if err != nil {
291 log.Printf("ERROR: Failed to open connection to SQL server: %q\n ", err)
292 panic(err)
293 }
294 for rows.Next() {
295 var id int
296 var image []byte
297 var create_ts time.Time
298 if err := rows.Scan(&id, &image, &create_ts); err != nil {
299 log.Printf("Error: failed to fetch from database: %q", e rr)
300 continue
301 }
302 filename := fmt.Sprintf("../../../inout/image-%d.png", id)
303 if _, err := os.Stat(filename); os.IsExist(err) {
304 log.Printf("Skipping write since file exists: %q", filen ame)
305 continue
306 }
307 if err := ioutil.WriteFile(filename, image, 0666); err != nil {
308 log.Printf("Error: failed to write image file: %q", err)
309 }
310 }
261 } 311 }
262 312
263 // Titlebar is used in titlebar template expansion. 313 // Titlebar is used in titlebar template expansion.
264 type Titlebar struct { 314 type Titlebar struct {
265 GitHash string 315 GitHash string
266 GitInfo string 316 GitInfo string
267 } 317 }
268 318
269 // userCode is used in template expansion. 319 // userCode is used in template expansion.
270 type userCode struct { 320 type userCode struct {
271 Code string 321 Code string
272 Hash string 322 Hash string
323 Source int
273 Titlebar Titlebar 324 Titlebar Titlebar
274 } 325 }
275 326
276 // expandToFile expands the template and writes the result to the file. 327 // expandToFile expands the template and writes the result to the file.
277 func expandToFile(filename string, code string, t *template.Template) error { 328 func expandToFile(filename string, code string, t *template.Template) error {
278 f, err := os.Create(filename) 329 f, err := os.Create(filename)
279 if err != nil { 330 if err != nil {
280 return err 331 return err
281 } 332 }
282 defer f.Close() 333 defer f.Close()
283 return t.Execute(f, userCode{Code: code, Titlebar: Titlebar{GitHash: git Hash, GitInfo: gitInfo}}) 334 return t.Execute(f, userCode{Code: code, Titlebar: Titlebar{GitHash: git Hash, GitInfo: gitInfo}})
284 } 335 }
285 336
286 // expandCode expands the template into a file and calculate the MD5 hash. 337 // expandCode expands the template into a file and calculates the MD5 hash.
287 func expandCode(code string) (string, error) { 338 func expandCode(code string, source int) (string, error) {
288 h := md5.New() 339 h := md5.New()
289 h.Write([]byte(code)) 340 h.Write([]byte(code))
341 binary.Write(h, binary.LittleEndian, int64(source))
290 hash := fmt.Sprintf("%x", h.Sum(nil)) 342 hash := fmt.Sprintf("%x", h.Sum(nil))
291 // At this point we are running in skia/experimental/webtry, making cach e a 343 // At this point we are running in skia/experimental/webtry, making cach e a
292 // peer directory to skia. 344 // peer directory to skia.
293 // TODO(jcgregorio) Make all relative directories into flags. 345 // TODO(jcgregorio) Make all relative directories into flags.
294 err := expandToFile(fmt.Sprintf("../../../cache/%s.cpp", hash), code, co deTemplate) 346 err := expandToFile(fmt.Sprintf("../../../cache/%s.cpp", hash), code, co deTemplate)
295 return hash, err 347 return hash, err
296 } 348 }
297 349
298 // response is serialized to JSON as a response to POSTs. 350 // response is serialized to JSON as a response to POSTs.
299 type response struct { 351 type response struct {
(...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after
353 log.Printf("Error: %s\n%s", message, err.Error()) 405 log.Printf("Error: %s\n%s", message, err.Error())
354 resp, err := json.Marshal(m) 406 resp, err := json.Marshal(m)
355 if err != nil { 407 if err != nil {
356 http.Error(w, "Failed to serialize a response", 500) 408 http.Error(w, "Failed to serialize a response", 500)
357 return 409 return
358 } 410 }
359 w.Header().Set("Content-Type", "text/plain") 411 w.Header().Set("Content-Type", "text/plain")
360 w.Write(resp) 412 w.Write(resp)
361 } 413 }
362 414
363 func writeToDatabase(hash string, code string, workspaceName string) { 415 func writeToDatabase(hash string, code string, workspaceName string, source int) {
364 if db == nil { 416 if db == nil {
365 return 417 return
366 } 418 }
367 » if _, err := db.Exec("INSERT INTO webtry (code, hash) VALUES(?, ?)", cod e, hash); err != nil { 419 » if _, err := db.Exec("INSERT INTO webtry (code, hash, source) VALUES(?, ?, ?)", code, hash, source); err != nil {
368 log.Printf("ERROR: Failed to insert code into database: %q\n", e rr) 420 log.Printf("ERROR: Failed to insert code into database: %q\n", e rr)
369 } 421 }
370 if workspaceName != "" { 422 if workspaceName != "" {
371 » » if _, err := db.Exec("INSERT INTO workspacetry (name, hash) VALU ES(?, ?)", workspaceName, hash); err != nil { 423 » » if _, err := db.Exec("INSERT INTO workspacetry (name, hash, sour ce) VALUES(?, ?, ?)", workspaceName, hash, source); err != nil {
372 log.Printf("ERROR: Failed to insert into workspacetry ta ble: %q\n", err) 424 log.Printf("ERROR: Failed to insert into workspacetry ta ble: %q\n", err)
373 } 425 }
374 } 426 }
375 } 427 }
376 428
429 type Sources struct {
430 Id int `json:"id"`
431 }
432
433 // sourcesHandler serves up the PNG of a specific try.
434 func sourcesHandler(w http.ResponseWriter, r *http.Request) {
435 log.Printf("Sources Handler: %q\n", r.URL.Path)
436 if r.Method == "GET" {
437 rows, err := db.Query("SELECT id, create_ts FROM sources WHERE h idden=0 ORDER BY create_ts DESC")
438
439 if err != nil {
440 http.Error(w, fmt.Sprintf("Failed to query sources: %s." , err), 500)
441 }
442 sources := make([]Sources, 0, 0)
443 for rows.Next() {
444 var id int
445 var create_ts time.Time
446 if err := rows.Scan(&id, &create_ts); err != nil {
447 log.Printf("Error: failed to fetch from database : %q", err)
448 continue
449 }
450 sources = append(sources, Sources{Id: id})
451 }
452
453 resp, err := json.Marshal(sources)
454 if err != nil {
455 reportError(w, r, err, "Failed to serialize a response." )
456 return
457 }
458 w.Header().Set("Content-Type", "application/json")
459 w.Write(resp)
460
461 } else if r.Method == "POST" {
462 if err := r.ParseMultipartForm(1000000); err != nil {
463 http.Error(w, fmt.Sprintf("Failed to load image: %s.", e rr), 500)
464 return
465 }
466 if _, ok := r.MultipartForm.File["upload"]; !ok {
467 http.Error(w, "Invalid upload.", 500)
468 return
469 }
470 if len(r.MultipartForm.File["upload"]) != 1 {
471 http.Error(w, "Wrong number of uploads.", 500)
472 return
473 }
474 f, err := r.MultipartForm.File["upload"][0].Open()
475 if err != nil {
476 http.Error(w, fmt.Sprintf("Failed to load image: %s.", e rr), 500)
477 return
478 }
479 defer f.Close()
480 m, _, err := image.Decode(f)
mtklein 2014/05/28 17:36:50 Might consider leaving everything in its native fo
jcgregorio 2014/05/28 18:42:59 I started down that path originally, but it opens
481 if err != nil {
482 http.Error(w, fmt.Sprintf("Failed to decode image: %s.", err), 500)
483 return
484 }
485 var b bytes.Buffer
486 png.Encode(&b, m)
487 bounds := m.Bounds()
488 width := bounds.Max.Y - bounds.Min.Y
489 height := bounds.Max.X - bounds.Min.X
490 if _, err := db.Exec("INSERT INTO sources (image, width, height) VALUES(?, ?, ?)", b.Bytes(), width, height); err != nil {
491 log.Printf("ERROR: Failed to insert sources into databas e: %q\n", err)
492 http.Error(w, fmt.Sprintf("Failed to store image: %s.", err), 500)
493 return
494 }
495 go writeOutAllSourceImages()
496
497 // Now redirect back to where we came from.
498 http.Redirect(w, r, r.Referer(), 302)
499 } else {
500 http.NotFound(w, r)
501 return
502 }
503 }
504
377 // imageHandler serves up the PNG of a specific try. 505 // imageHandler serves up the PNG of a specific try.
378 func imageHandler(w http.ResponseWriter, r *http.Request) { 506 func imageHandler(w http.ResponseWriter, r *http.Request) {
379 log.Printf("Image Handler: %q\n", r.URL.Path) 507 log.Printf("Image Handler: %q\n", r.URL.Path)
380 if r.Method != "GET" { 508 if r.Method != "GET" {
381 http.NotFound(w, r) 509 http.NotFound(w, r)
382 return 510 return
383 } 511 }
384 match := imageLink.FindStringSubmatch(r.URL.Path) 512 match := imageLink.FindStringSubmatch(r.URL.Path)
385 if len(match) != 2 { 513 if len(match) != 2 {
386 http.NotFound(w, r) 514 http.NotFound(w, r)
387 return 515 return
388 } 516 }
389 filename := match[1] 517 filename := match[1]
390 w.Header().Set("Content-Type", "image/png") 518 w.Header().Set("Content-Type", "image/png")
391 http.ServeFile(w, r, fmt.Sprintf("../../../inout/%s", filename)) 519 http.ServeFile(w, r, fmt.Sprintf("../../../inout/%s", filename))
392 } 520 }
393 521
394 type Try struct { 522 type Try struct {
395 Hash string `json:"hash"` 523 Hash string `json:"hash"`
524 Source int
396 CreateTS string `json:"create_ts"` 525 CreateTS string `json:"create_ts"`
397 } 526 }
398 527
399 type Recent struct { 528 type Recent struct {
400 Tries []Try 529 Tries []Try
401 Titlebar Titlebar 530 Titlebar Titlebar
402 } 531 }
403 532
404 // recentHandler shows the last 20 tries. 533 // recentHandler shows the last 20 tries.
405 func recentHandler(w http.ResponseWriter, r *http.Request) { 534 func recentHandler(w http.ResponseWriter, r *http.Request) {
(...skipping 18 matching lines...) Expand all
424 w.Header().Set("Content-Type", "text/html") 553 w.Header().Set("Content-Type", "text/html")
425 if err := recentTemplate.Execute(w, Recent{Tries: recent, Titlebar: Titl ebar{GitHash: gitHash, GitInfo: gitInfo}}); err != nil { 554 if err := recentTemplate.Execute(w, Recent{Tries: recent, Titlebar: Titl ebar{GitHash: gitHash, GitInfo: gitInfo}}); err != nil {
426 log.Printf("ERROR: Failed to expand template: %q\n", err) 555 log.Printf("ERROR: Failed to expand template: %q\n", err)
427 } 556 }
428 } 557 }
429 558
430 type Workspace struct { 559 type Workspace struct {
431 Name string 560 Name string
432 Code string 561 Code string
433 Hash string 562 Hash string
563 Source int
434 Tries []Try 564 Tries []Try
435 Titlebar Titlebar 565 Titlebar Titlebar
436 } 566 }
437 567
438 // newWorkspace generates a new random workspace name and stores it in the datab ase. 568 // newWorkspace generates a new random workspace name and stores it in the datab ase.
439 func newWorkspace() (string, error) { 569 func newWorkspace() (string, error) {
440 for i := 0; i < 10; i++ { 570 for i := 0; i < 10; i++ {
441 adj := workspaceNameAdj[rand.Intn(len(workspaceNameAdj))] 571 adj := workspaceNameAdj[rand.Intn(len(workspaceNameAdj))]
442 noun := workspaceNameNoun[rand.Intn(len(workspaceNameNoun))] 572 noun := workspaceNameNoun[rand.Intn(len(workspaceNameNoun))]
443 suffix := rand.Intn(1000) 573 suffix := rand.Intn(1000)
444 name := fmt.Sprintf("%s-%s-%d", adj, noun, suffix) 574 name := fmt.Sprintf("%s-%s-%d", adj, noun, suffix)
445 if _, err := db.Exec("INSERT INTO workspace (name) VALUES(?)", n ame); err == nil { 575 if _, err := db.Exec("INSERT INTO workspace (name) VALUES(?)", n ame); err == nil {
446 return name, nil 576 return name, nil
447 } else { 577 } else {
448 log.Printf("ERROR: Failed to insert workspace into datab ase: %q\n", err) 578 log.Printf("ERROR: Failed to insert workspace into datab ase: %q\n", err)
449 } 579 }
450 } 580 }
451 return "", fmt.Errorf("Failed to create a new workspace") 581 return "", fmt.Errorf("Failed to create a new workspace")
452 } 582 }
453 583
454 // getCode returns the code for a given hash, or the empty string if not found. 584 // getCode returns the code for a given hash, or the empty string if not found.
455 func getCode(hash string) (string, error) { 585 func getCode(hash string) (string, int, error) {
456 code := "" 586 code := ""
457 » if err := db.QueryRow("SELECT code FROM webtry WHERE hash=?", hash).Scan (&code); err != nil { 587 » source := 0
588 » if err := db.QueryRow("SELECT code, source FROM webtry WHERE hash=?", ha sh).Scan(&code, &source); err != nil {
458 log.Printf("ERROR: Code for hash is missing: %q\n", err) 589 log.Printf("ERROR: Code for hash is missing: %q\n", err)
459 » » return code, err 590 » » return code, source, err
460 } 591 }
461 » return code, nil 592 » return code, source, nil
462 } 593 }
463 594
464 func workspaceHandler(w http.ResponseWriter, r *http.Request) { 595 func workspaceHandler(w http.ResponseWriter, r *http.Request) {
465 log.Printf("Workspace Handler: %q\n", r.URL.Path) 596 log.Printf("Workspace Handler: %q\n", r.URL.Path)
466 if r.Method == "GET" { 597 if r.Method == "GET" {
467 tries := []Try{} 598 tries := []Try{}
468 match := workspaceLink.FindStringSubmatch(r.URL.Path) 599 match := workspaceLink.FindStringSubmatch(r.URL.Path)
469 name := "" 600 name := ""
470 if len(match) == 2 { 601 if len(match) == 2 {
471 name = match[1] 602 name = match[1]
472 » » » rows, err := db.Query("SELECT create_ts, hash FROM works pacetry WHERE name=? ORDER BY create_ts", name) 603 » » » rows, err := db.Query("SELECT create_ts, hash, source FR OM workspacetry WHERE name=? ORDER BY create_ts", name)
473 if err != nil { 604 if err != nil {
474 reportError(w, r, err, "Failed to select.") 605 reportError(w, r, err, "Failed to select.")
475 return 606 return
476 } 607 }
477 for rows.Next() { 608 for rows.Next() {
478 var hash string 609 var hash string
479 var create_ts time.Time 610 var create_ts time.Time
480 » » » » if err := rows.Scan(&create_ts, &hash); err != n il { 611 » » » » var source int
612 » » » » if err := rows.Scan(&create_ts, &hash, &source); err != nil {
481 log.Printf("Error: failed to fetch from database: %q", err) 613 log.Printf("Error: failed to fetch from database: %q", err)
482 continue 614 continue
483 } 615 }
484 » » » » tries = append(tries, Try{Hash: hash, CreateTS: create_ts.Format("2006-02-01")}) 616 » » » » tries = append(tries, Try{Hash: hash, Source: so urce, CreateTS: create_ts.Format("2006-02-01")})
485 } 617 }
486 } 618 }
487 var code string 619 var code string
488 var hash string 620 var hash string
621 source := 0
489 if len(tries) == 0 { 622 if len(tries) == 0 {
490 code = DEFAULT_SAMPLE 623 code = DEFAULT_SAMPLE
491 } else { 624 } else {
492 hash = tries[len(tries)-1].Hash 625 hash = tries[len(tries)-1].Hash
493 » » » code, _ = getCode(hash) 626 » » » code, source, _ = getCode(hash)
494 } 627 }
495 w.Header().Set("Content-Type", "text/html") 628 w.Header().Set("Content-Type", "text/html")
496 » » if err := workspaceTemplate.Execute(w, Workspace{Tries: tries, C ode: code, Name: name, Hash: hash, Titlebar: Titlebar{GitHash: gitHash, GitInfo: gitInfo}}); err != nil { 629 » » if err := workspaceTemplate.Execute(w, Workspace{Tries: tries, C ode: code, Name: name, Hash: hash, Source: source, Titlebar: Titlebar{GitHash: g itHash, GitInfo: gitInfo}}); err != nil {
497 log.Printf("ERROR: Failed to expand template: %q\n", err ) 630 log.Printf("ERROR: Failed to expand template: %q\n", err )
498 } 631 }
499 } else if r.Method == "POST" { 632 } else if r.Method == "POST" {
500 name, err := newWorkspace() 633 name, err := newWorkspace()
501 if err != nil { 634 if err != nil {
502 http.Error(w, "Failed to create a new workspace.", 500) 635 http.Error(w, "Failed to create a new workspace.", 500)
503 return 636 return
504 } 637 }
505 http.Redirect(w, r, "/w/"+name, 302) 638 http.Redirect(w, r, "/w/"+name, 302)
506 } 639 }
507 } 640 }
508 641
509 // hasPreProcessor returns true if any line in the code begins with a # char. 642 // hasPreProcessor returns true if any line in the code begins with a # char.
510 func hasPreProcessor(code string) bool { 643 func hasPreProcessor(code string) bool {
511 lines := strings.Split(code, "\n") 644 lines := strings.Split(code, "\n")
512 for _, s := range lines { 645 for _, s := range lines {
513 if strings.HasPrefix(strings.TrimSpace(s), "#") { 646 if strings.HasPrefix(strings.TrimSpace(s), "#") {
514 return true 647 return true
515 } 648 }
516 } 649 }
517 return false 650 return false
518 } 651 }
519 652
520 type TryRequest struct { 653 type TryRequest struct {
521 » Code string `json:"code"` 654 » Code string `json:"code"`
522 » Name string `json:"name"` // Optional name of the workspace the code is in. 655 » Name string `json:"name"` // Optional name of the workspace the code is in.
656 » Source int `json:"source"` // ID of the source image, 0 if none.
523 } 657 }
524 658
525 // iframeHandler handles the GET and POST of the main page. 659 // iframeHandler handles the GET and POST of the main page.
526 func iframeHandler(w http.ResponseWriter, r *http.Request) { 660 func iframeHandler(w http.ResponseWriter, r *http.Request) {
527 log.Printf("IFrame Handler: %q\n", r.URL.Path) 661 log.Printf("IFrame Handler: %q\n", r.URL.Path)
528 if r.Method != "GET" { 662 if r.Method != "GET" {
529 http.NotFound(w, r) 663 http.NotFound(w, r)
530 return 664 return
531 } 665 }
532 match := iframeLink.FindStringSubmatch(r.URL.Path) 666 match := iframeLink.FindStringSubmatch(r.URL.Path)
533 if len(match) != 2 { 667 if len(match) != 2 {
534 http.NotFound(w, r) 668 http.NotFound(w, r)
535 return 669 return
536 } 670 }
537 hash := match[1] 671 hash := match[1]
538 if db == nil { 672 if db == nil {
539 http.NotFound(w, r) 673 http.NotFound(w, r)
540 return 674 return
541 } 675 }
542 var code string 676 var code string
543 » code, err := getCode(hash) 677 » code, source, err := getCode(hash)
544 if err != nil { 678 if err != nil {
545 http.NotFound(w, r) 679 http.NotFound(w, r)
546 return 680 return
547 } 681 }
548 // Expand the template. 682 // Expand the template.
549 w.Header().Set("Content-Type", "text/html") 683 w.Header().Set("Content-Type", "text/html")
550 » if err := iframeTemplate.Execute(w, userCode{Code: code, Hash: hash}); e rr != nil { 684 » if err := iframeTemplate.Execute(w, userCode{Code: code, Hash: hash, Sou rce: source}); err != nil {
551 log.Printf("ERROR: Failed to expand template: %q\n", err) 685 log.Printf("ERROR: Failed to expand template: %q\n", err)
552 } 686 }
553 } 687 }
554 688
555 type TryInfo struct { 689 type TryInfo struct {
556 » Hash string `json:"hash"` 690 » Hash string `json:"hash"`
557 » Code string `json:"code"` 691 » Code string `json:"code"`
692 » Source int `json:"source"`
558 } 693 }
559 694
560 // tryInfoHandler returns information about a specific try. 695 // tryInfoHandler returns information about a specific try.
561 func tryInfoHandler(w http.ResponseWriter, r *http.Request) { 696 func tryInfoHandler(w http.ResponseWriter, r *http.Request) {
562 log.Printf("Try Info Handler: %q\n", r.URL.Path) 697 log.Printf("Try Info Handler: %q\n", r.URL.Path)
563 if r.Method != "GET" { 698 if r.Method != "GET" {
564 http.NotFound(w, r) 699 http.NotFound(w, r)
565 return 700 return
566 } 701 }
567 match := tryInfoLink.FindStringSubmatch(r.URL.Path) 702 match := tryInfoLink.FindStringSubmatch(r.URL.Path)
568 if len(match) != 2 { 703 if len(match) != 2 {
569 http.NotFound(w, r) 704 http.NotFound(w, r)
570 return 705 return
571 } 706 }
572 hash := match[1] 707 hash := match[1]
573 » code, err := getCode(hash) 708 » code, source, err := getCode(hash)
574 if err != nil { 709 if err != nil {
575 http.NotFound(w, r) 710 http.NotFound(w, r)
576 return 711 return
577 } 712 }
578 m := TryInfo{ 713 m := TryInfo{
579 » » Hash: hash, 714 » » Hash: hash,
580 » » Code: code, 715 » » Code: code,
716 » » Source: source,
581 } 717 }
582 resp, err := json.Marshal(m) 718 resp, err := json.Marshal(m)
583 if err != nil { 719 if err != nil {
584 reportError(w, r, err, "Failed to serialize a response.") 720 reportError(w, r, err, "Failed to serialize a response.")
585 return 721 return
586 } 722 }
587 w.Header().Set("Content-Type", "application/json") 723 w.Header().Set("Content-Type", "application/json")
588 w.Write(resp) 724 w.Write(resp)
589 } 725 }
590 726
591 func cleanCompileOutput(s, hash string) string { 727 func cleanCompileOutput(s, hash string) string {
592 old := "../../../cache/" + hash + ".cpp:" 728 old := "../../../cache/" + hash + ".cpp:"
593 log.Printf("INFO: replacing %q\n", old) 729 log.Printf("INFO: replacing %q\n", old)
594 return strings.Replace(s, old, "usercode.cpp:", -1) 730 return strings.Replace(s, old, "usercode.cpp:", -1)
595 } 731 }
596 732
597 // mainHandler handles the GET and POST of the main page. 733 // mainHandler handles the GET and POST of the main page.
598 func mainHandler(w http.ResponseWriter, r *http.Request) { 734 func mainHandler(w http.ResponseWriter, r *http.Request) {
599 log.Printf("Main Handler: %q\n", r.URL.Path) 735 log.Printf("Main Handler: %q\n", r.URL.Path)
600 if r.Method == "GET" { 736 if r.Method == "GET" {
601 code := DEFAULT_SAMPLE 737 code := DEFAULT_SAMPLE
738 source := 0
602 match := directLink.FindStringSubmatch(r.URL.Path) 739 match := directLink.FindStringSubmatch(r.URL.Path)
603 var hash string 740 var hash string
604 if len(match) == 2 && r.URL.Path != "/" { 741 if len(match) == 2 && r.URL.Path != "/" {
605 hash = match[1] 742 hash = match[1]
606 if db == nil { 743 if db == nil {
607 http.NotFound(w, r) 744 http.NotFound(w, r)
608 return 745 return
609 } 746 }
610 // Update 'code' with the code found in the database. 747 // Update 'code' with the code found in the database.
611 » » » if err := db.QueryRow("SELECT code FROM webtry WHERE has h=?", hash).Scan(&code); err != nil { 748 » » » if err := db.QueryRow("SELECT code, source FROM webtry W HERE hash=?", hash).Scan(&code, &source); err != nil {
612 http.NotFound(w, r) 749 http.NotFound(w, r)
613 return 750 return
614 } 751 }
615 } 752 }
616 // Expand the template. 753 // Expand the template.
617 w.Header().Set("Content-Type", "text/html") 754 w.Header().Set("Content-Type", "text/html")
618 » » if err := indexTemplate.Execute(w, userCode{Code: code, Hash: ha sh, Titlebar: Titlebar{GitHash: gitHash, GitInfo: gitInfo}}); err != nil { 755 » » if err := indexTemplate.Execute(w, userCode{Code: code, Hash: ha sh, Source: source, Titlebar: Titlebar{GitHash: gitHash, GitInfo: gitInfo}}); er r != nil {
619 log.Printf("ERROR: Failed to expand template: %q\n", err ) 756 log.Printf("ERROR: Failed to expand template: %q\n", err )
620 } 757 }
621 } else if r.Method == "POST" { 758 } else if r.Method == "POST" {
622 w.Header().Set("Content-Type", "application/json") 759 w.Header().Set("Content-Type", "application/json")
623 buf := bytes.NewBuffer(make([]byte, 0, MAX_TRY_SIZE)) 760 buf := bytes.NewBuffer(make([]byte, 0, MAX_TRY_SIZE))
624 n, err := buf.ReadFrom(r.Body) 761 n, err := buf.ReadFrom(r.Body)
625 if err != nil { 762 if err != nil {
626 reportTryError(w, r, err, "Failed to read a request body .", "") 763 reportTryError(w, r, err, "Failed to read a request body .", "")
627 return 764 return
628 } 765 }
629 if n == MAX_TRY_SIZE { 766 if n == MAX_TRY_SIZE {
630 err := fmt.Errorf("Code length equal to, or exceeded, %d ", MAX_TRY_SIZE) 767 err := fmt.Errorf("Code length equal to, or exceeded, %d ", MAX_TRY_SIZE)
631 reportTryError(w, r, err, "Code too large.", "") 768 reportTryError(w, r, err, "Code too large.", "")
632 return 769 return
633 } 770 }
634 request := TryRequest{} 771 request := TryRequest{}
635 if err := json.Unmarshal(buf.Bytes(), &request); err != nil { 772 if err := json.Unmarshal(buf.Bytes(), &request); err != nil {
636 reportTryError(w, r, err, "Coulnd't decode JSON.", "") 773 reportTryError(w, r, err, "Coulnd't decode JSON.", "")
637 return 774 return
638 } 775 }
639 if hasPreProcessor(request.Code) { 776 if hasPreProcessor(request.Code) {
640 err := fmt.Errorf("Found preprocessor macro in code.") 777 err := fmt.Errorf("Found preprocessor macro in code.")
641 reportTryError(w, r, err, "Preprocessor macros aren't al lowed.", "") 778 reportTryError(w, r, err, "Preprocessor macros aren't al lowed.", "")
642 return 779 return
643 } 780 }
644 » » hash, err := expandCode(LineNumbers(request.Code)) 781 » » hash, err := expandCode(LineNumbers(request.Code), request.Sourc e)
645 if err != nil { 782 if err != nil {
646 reportTryError(w, r, err, "Failed to write the code to c ompile.", hash) 783 reportTryError(w, r, err, "Failed to write the code to c ompile.", hash)
647 return 784 return
648 } 785 }
649 » » writeToDatabase(hash, request.Code, request.Name) 786 » » writeToDatabase(hash, request.Code, request.Name, request.Source )
650 message, err := doCmd(fmt.Sprintf(RESULT_COMPILE, hash, hash), t rue) 787 message, err := doCmd(fmt.Sprintf(RESULT_COMPILE, hash, hash), t rue)
651 if err != nil { 788 if err != nil {
652 message = cleanCompileOutput(message, hash) 789 message = cleanCompileOutput(message, hash)
653 reportTryError(w, r, err, message, hash) 790 reportTryError(w, r, err, message, hash)
654 return 791 return
655 } 792 }
656 linkMessage, err := doCmd(fmt.Sprintf(LINK, hash, hash), true) 793 linkMessage, err := doCmd(fmt.Sprintf(LINK, hash, hash), true)
657 if err != nil { 794 if err != nil {
658 linkMessage = cleanCompileOutput(linkMessage, hash) 795 linkMessage = cleanCompileOutput(linkMessage, hash)
659 reportTryError(w, r, err, linkMessage, hash) 796 reportTryError(w, r, err, linkMessage, hash)
660 return 797 return
661 } 798 }
662 message += linkMessage 799 message += linkMessage
663 cmd := hash + " --out " + hash + ".png" 800 cmd := hash + " --out " + hash + ".png"
801 if request.Source > 0 {
802 cmd += fmt.Sprintf(" --source image-%d.png", request.So urce)
803 }
664 if *useChroot { 804 if *useChroot {
665 cmd = "schroot -c webtry --directory=/inout -- /inout/" + cmd 805 cmd = "schroot -c webtry --directory=/inout -- /inout/" + cmd
666 } else { 806 } else {
667 abs, err := filepath.Abs("../../../inout") 807 abs, err := filepath.Abs("../../../inout")
668 if err != nil { 808 if err != nil {
669 reportTryError(w, r, err, "Failed to find execut able directory.", hash) 809 reportTryError(w, r, err, "Failed to find execut able directory.", hash)
670 return 810 return
671 } 811 }
672 cmd = abs + "/" + cmd 812 cmd = abs + "/" + cmd
673 } 813 }
(...skipping 25 matching lines...) Expand all
699 } 839 }
700 } 840 }
701 841
702 func main() { 842 func main() {
703 flag.Parse() 843 flag.Parse()
704 http.HandleFunc("/i/", autogzip.HandleFunc(imageHandler)) 844 http.HandleFunc("/i/", autogzip.HandleFunc(imageHandler))
705 http.HandleFunc("/w/", autogzip.HandleFunc(workspaceHandler)) 845 http.HandleFunc("/w/", autogzip.HandleFunc(workspaceHandler))
706 http.HandleFunc("/recent/", autogzip.HandleFunc(recentHandler)) 846 http.HandleFunc("/recent/", autogzip.HandleFunc(recentHandler))
707 http.HandleFunc("/iframe/", autogzip.HandleFunc(iframeHandler)) 847 http.HandleFunc("/iframe/", autogzip.HandleFunc(iframeHandler))
708 http.HandleFunc("/json/", autogzip.HandleFunc(tryInfoHandler)) 848 http.HandleFunc("/json/", autogzip.HandleFunc(tryInfoHandler))
849 http.HandleFunc("/sources/", autogzip.HandleFunc(sourcesHandler))
709 850
710 // Resources are served directly 851 // Resources are served directly
711 // TODO add support for caching/etags/gzip 852 // TODO add support for caching/etags/gzip
712 http.Handle("/res/", autogzip.Handle(http.FileServer(http.Dir("./")))) 853 http.Handle("/res/", autogzip.Handle(http.FileServer(http.Dir("./"))))
713 854
714 // TODO Break out /c/ as it's own handler. 855 // TODO Break out /c/ as it's own handler.
715 http.HandleFunc("/", autogzip.HandleFunc(mainHandler)) 856 http.HandleFunc("/", autogzip.HandleFunc(mainHandler))
716 log.Fatal(http.ListenAndServe(*port, nil)) 857 log.Fatal(http.ListenAndServe(*port, nil))
717 } 858 }
OLDNEW
« experimental/webtry/DESIGN.md ('K') | « experimental/webtry/templates/template.cpp ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698