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/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 Loading... | |
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 Loading... | |
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 Loading... | |
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 Loading... | |
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 Loading... | |
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 } |
OLD | NEW |