Chromium Code Reviews| 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 |