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