Add the ability to select a source image to use in the code.

A much farther ranging change than I suspected.

Basically add a 'source' integer to every Try, store that in the database with every Try, add the source to the computation of the hash, and load and use the 'source' value when navigating history.

BUG=skia:
R=mtklein@google.com

Author: jcgregorio@google.com

Review URL: https://codereview.chromium.org/294903017

git-svn-id: http://skia.googlecode.com/svn/trunk@14960 2bbb7eff-a529-9590-31e7-b0007b416f81
This commit is contained in:
commit-bot@chromium.org 2014-05-29 15:58:00 +00:00
parent cba73780bb
commit 0a7e5b7554
8 changed files with 424 additions and 74 deletions

View File

@ -133,34 +133,51 @@ Initial setup of the database, the user, and the only table:
CREATE DATABASE webtry;
USE webtry;
CREATE USER 'webtry'@'%' IDENTIFIED BY '<password is in valentine>';
GRANT SELECT, INSERT, UPDATE ON webtry.webtry TO 'webtry'@'%';
GRANT SELECT, INSERT, UPDATE ON webtry.workspace TO 'webtry'@'%';
GRANT SELECT, INSERT, UPDATE ON webtry.workspacetry TO 'webtry'@'%';
GRANT SELECT, INSERT, UPDATE ON webtry.webtry TO 'webtry'@'%';
GRANT SELECT, INSERT, UPDATE ON webtry.workspace TO 'webtry'@'%';
GRANT SELECT, INSERT, UPDATE ON webtry.workspacetry TO 'webtry'@'%';
GRANT SELECT, INSERT, UPDATE ON webtry.source_images TO 'webtry'@'%';
// If this gets changed also update the sqlite create statement in webtry.go.
CREATE TABLE webtry (
code TEXT DEFAULT '' NOT NULL,
create_ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
hash CHAR(64) DEFAULT '' NOT NULL,
PRIMARY KEY(hash)
code TEXT DEFAULT '' NOT NULL,
create_ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
hash CHAR(64) DEFAULT '' NOT NULL,
source_image_id INTEGER DEFAULT 0 NOT NULL,
PRIMARY KEY(hash),
FOREIGN KEY (source) REFERENCES sources(id)
);
CREATE TABLE workspace (
name CHAR(64) DEFAULT '' NOT NULL,
create_ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
PRIMARY KEY(name)
PRIMARY KEY(name),
);
CREATE TABLE workspacetry (
name CHAR(64) DEFAULT '' NOT NULL,
create_ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
hash CHAR(64) DEFAULT '' NOT NULL,
hidden INTEGER DEFAULT 0 NOT NULL,
name CHAR(64) DEFAULT '' NOT NULL,
create_ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
hash CHAR(64) DEFAULT '' NOT NULL,
source_image_id INTEGER DEFAULT 0 NOT NULL,
hidden INTEGER DEFAULT 0 NOT NULL,
FOREIGN KEY (name) REFERENCES workspace(name)
FOREIGN KEY (name) REFERENCES workspace(name),
);
CREATE TABLE source_images (
id INTEGER PRIMARY KEY NOT NULL,
image MEDIUMBLOB DEFAULT '' NOT NULL, -- Stored as PNG.
width INTEGER DEFAULT 0 NOT NULL,
height INTEGER DEFAULT 0 NOT NULL,
create_ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
hidden INTEGER DEFAULT 0 NOT NULL
);
ALTER TABLE webtry ADD COLUMN source_image_id INTEGER DEFAULT 0 NOT NULL AFTER hash;
ALTER TABLE workspacetry ADD COLUMN source_image_id INTEGER DEFAULT 0 NOT NULL AFTER hash;
Common queries webtry.go will use:
INSERT INTO webtry (code, hash) VALUES('int i = 0;...', 'abcdef...');
@ -183,6 +200,10 @@ Common queries for workspaces:
SELECT name FROM workspace GROUP BY name;
Common queries for sources:
SELECT id, image, width, height, create_ts FROM source_images ORDER BY create_ts DESC LIMIT 100;
Password for the database will be stored in the metadata instance, if the
metadata server can't be found, i.e. running locally, then a local sqlite
database will be used. To see the current password stored in metadata and the
@ -202,6 +223,29 @@ the metadata server:
N.B. If you need to change the MySQL password that webtry uses, you must change
it both in MySQL and the value stored in the metadata server.
Source Images
-------------
For every try the user can select an optional source image to use as an input.
The id of the source image is just an integer and is stored in the database
along with the other try information, such as the code.
The actual image itself is also stored in a separate table, 'sources', in the
database. On startup we check that all the images are available in 'inout',
and write out the images if not. Since they are all written to 'inout' we can
use the same /i/ image handler to serve them.
When a user uploads an image it is decoded and converted to PNG and stored
as a binary blob in the database.
The bitmap is available to user code as a module level variable:
SkBitmap source;
The bitmap is read, decoded and stored in source before the seccomp jail is
instantiated.
Squid
-----

View File

@ -6,6 +6,7 @@
#include "SkData.h"
#include "SkForceLinking.h"
#include "SkGraphics.h"
#include "SkImageDecoder.h"
#include "SkImageEncoder.h"
#include "SkImageInfo.h"
#include "SkStream.h"
@ -16,6 +17,10 @@
__SK_FORCE_IMAGE_DECODER_LINKING;
DEFINE_string(out, "", "Filename of the PNG to write to.");
DEFINE_string(source, "", "Filename of the source image.");
// Defined in template.cpp.
extern SkBitmap source;
static bool install_syscall_filter() {
struct sock_filter filter[] = {
@ -89,6 +94,13 @@ int main(int argc, char** argv) {
perror("The --out flag must have an argument.");
return 1;
}
if (FLAGS_source.count() == 1) {
if (!SkImageDecoder::DecodeFile(FLAGS_source[0], &source)) {
perror("Unable to read the source image.");
}
}
SkFILEWStream stream(FLAGS_out[0]);
SkImageInfo info = SkImageInfo::MakeN32(256, 256, kPremul_SkAlphaType);

View File

@ -80,6 +80,38 @@ pre, code {
float: none;
}
#chooseList {
display: flex;
flex-flow: row wrap;
}
#chooseSource {
display: none;
background: ivory;
padding: 1em;
border: solid lightgray 2px;
}
#chooseSource.show {
display: block;
}
#selectedSource {
display: none;
}
#selectedSource.show {
display: block;
}
#sourceCode {
display: none;
}
#sourceCode.show {
display: block;
}
#gitInfo {
float: right;
font-size: 70%;

View File

@ -18,17 +18,127 @@
*/
(function() {
function onLoad() {
var run = document.getElementById('run');
var permalink = document.getElementById('permalink');
var embed = document.getElementById('embed');
var embedButton = document.getElementById('embedButton');
var code = document.getElementById('code');
var output = document.getElementById('output');
var stdout = document.getElementById('stdout');
var img = document.getElementById('img');
var tryHistory = document.getElementById('tryHistory');
var parser = new DOMParser();
var tryTemplate = document.getElementById('tryTemplate');
var run = document.getElementById('run');
var permalink = document.getElementById('permalink');
var embed = document.getElementById('embed');
var embedButton = document.getElementById('embedButton');
var code = document.getElementById('code');
var output = document.getElementById('output');
var stdout = document.getElementById('stdout');
var img = document.getElementById('img');
var tryHistory = document.getElementById('tryHistory');
var parser = new DOMParser();
var tryTemplate = document.getElementById('tryTemplate');
var sourcesTemplate = document.getElementById('sourcesTemplate');
var enableSource = document.getElementById('enableSource');
var selectedSource = document.getElementById('selectedSource');
var sourceCode = document.getElementById('sourceCode');
var chooseSource = document.getElementById('chooseSource');
var chooseList = document.getElementById('chooseList');
// Id of the source image to use, 0 if no source image is used.
var sourceId = 0;
sourceId = parseInt(enableSource.getAttribute('data-id'));
if (sourceId) {
sourceSelectByID(sourceId);
}
function beginWait() {
document.body.classList.add('waiting');
run.disabled = true;
}
function endWait() {
document.body.classList.remove('waiting');
run.disabled = false;
}
function sourceSelectByID(id) {
sourceId = id;
if (id > 0) {
enableSource.checked = true;
selectedSource.innerHTML = '<img with=64 height=64 src="/i/image-'+sourceId+'.png" />';
selectedSource.classList.add('show');
sourceCode.classList.add('show');
chooseSource.classList.remove('show');
} else {
enableSource.checked = false;
selectedSource.classList.remove('show');
sourceCode.classList.remove('show');
}
}
/**
* A selection has been made in the choiceList.
*/
function sourceSelect() {
sourceSelectByID(parseInt(this.getAttribute('data-id')));
}
/**
* Callback when the loading of the image sources is complete.
*
* Fills in the list of images from the data returned.
*/
function sourcesComplete(e) {
endWait();
// The response is JSON of the form:
// [
// {"id": 1},
// {"id": 3},
// ...
// ]
body = JSON.parse(e.target.response);
// Clear out the old list if present.
while (chooseList.firstChild) {
chooseList.removeChild(chooseList.firstChild);
}
body.forEach(function(source) {
var id = 'i'+source.id;
var imgsrc = '/i/image-'+source.id+'.png';
var clone = sourcesTemplate.content.cloneNode(true);
clone.querySelector('img').src = imgsrc;
clone.querySelector('button').setAttribute('id', id);
clone.querySelector('button').setAttribute('data-id', source.id);
chooseList.insertBefore(clone, chooseList.firstChild);
chooseList.querySelector('#'+id).addEventListener('click', sourceSelect, true);
});
chooseSource.classList.add('show');
}
/**
* Toggle the use of a source image, or select a new source image.
*
* If enabling source images then load the list of available images via
* XHR.
*/
function sourceClick(e) {
selectedSource.classList.remove('show');
sourceCode.classList.remove('show');
if (enableSource.checked) {
beginWait();
var req = new XMLHttpRequest();
req.addEventListener('load', sourcesComplete);
req.addEventListener('error', xhrError);
req.overrideMimeType('application/json');
req.open('GET', '/sources/', true);
req.send();
} else {
sourceId = 0;
}
}
enableSource.addEventListener('click', sourceClick, true);
selectedSource.addEventListener('click', sourceClick, true);
var editor = CodeMirror.fromTextArea(code, {
theme: "default",
@ -42,17 +152,6 @@
editor.setSize(editor.defaultCharWidth() * code.cols,
editor.defaultTextHeight() * code.rows);
function beginWait() {
document.body.classList.add('waiting');
run.disabled = true;
}
function endWait() {
document.body.classList.remove('waiting');
run.disabled = false;
}
/**
* Callback when there's an XHR error.
@ -100,6 +199,7 @@
code.value = body.code;
editor.setValue(body.code);
img.src = '/i/'+body.hash+'.png';
sourceSelectByID(body.source);
if (permalink) {
permalink.href = '/c/' + body.hash;
}
@ -172,7 +272,7 @@
req.overrideMimeType('application/json');
req.open('POST', '/', true);
req.setRequestHeader('content-type', 'application/json');
req.send(JSON.stringify({'code': editor.getValue(), 'name': workspaceName}));
req.send(JSON.stringify({'code': editor.getValue(), 'name': workspaceName, 'source': sourceId}));
}
run.addEventListener('click', onSubmitCode);

View File

@ -7,6 +7,7 @@
#include "SkStream.h"
#include "SkSurface.h"
SkBitmap source;
void draw(SkCanvas* canvas) {
#line 1

View File

@ -1,5 +1,23 @@
<section id=content>
<template id=sourcesTemplate>
<button id="" class=source><img width=64 height=64 src=''></button>
</template>
<input type="checkbox" id="enableSource" data-id="{{.Source}}"> Use an input bitmap.
<br>
<button id=selectedSource></button>
<pre id=sourceCode>SkBitmap source;</pre>
<div id=chooseSource>
Choose an image below or upload a new one to use as an input bitmap.
<div id="chooseList">
</div>
<form action="/sources/" method="post" accept-charset="utf-8" enctype="multipart/form-data">
<input type="file" accept="image/*" name="upload" value="" id="upload">
<input type="submit" value="Add Image">
</form>
</div>
<pre>
<textarea spellcheck=false name='code' id='code' rows='15' cols='100'>{{.Code}}</textarea>
</pre>
@ -9,6 +27,7 @@
<input type='button' value='Embed' id='embedButton' disabled/>
<input type="text" value="" id="embed" readonly style="display:none;">
<br>
<p>
<img touch-action='none' class='zoom' id='img' src='{{if .Hash}}/i/{{.Hash}}.png{{end}}'/>

View File

@ -158,4 +158,6 @@
#include "SkXfermode.h"
#include "SkXfermodeImageFilter.h"
SkBitmap source;
{{.Code}}

View File

@ -5,10 +5,15 @@ import (
"crypto/md5"
"database/sql"
"encoding/base64"
"encoding/binary"
"encoding/json"
"flag"
"fmt"
htemplate "html/template"
"image"
_ "image/gif"
_ "image/jpeg"
"image/png"
"io/ioutil"
"log"
"math/rand"
@ -70,7 +75,7 @@ var (
iframeLink = regexp.MustCompile("^/iframe/([a-f0-9]+)$")
// imageLink is the regex that matches URLs paths that are direct links to PNGs.
imageLink = regexp.MustCompile("^/i/([a-f0-9]+.png)$")
imageLink = regexp.MustCompile("^/i/([a-z0-9-]+.png)$")
// tryInfoLink is the regex that matches URLs paths that are direct links to data about a single try.
tryInfoLink = regexp.MustCompile("^/json/([a-f0-9]+)$")
@ -221,14 +226,28 @@ func init() {
log.Printf("ERROR: Failed to open: %q\n", err)
panic(err)
}
sql := `CREATE TABLE webtry (
code TEXT DEFAULT '' NOT NULL,
create_ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
hash CHAR(64) DEFAULT '' NOT NULL,
sql := `CREATE TABLE source_images (
id INTEGER PRIMARY KEY NOT NULL,
image MEDIUMBLOB DEFAULT '' NOT NULL, -- formatted as a PNG.
width INTEGER DEFAULT 0 NOT NULL,
height INTEGER DEFAULT 0 NOT NULL,
create_ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
hidden INTEGER DEFAULT 0 NOT NULL
)`
_, err = db.Exec(sql)
log.Printf("Info: status creating sqlite table for sources: %q\n", err)
sql = `CREATE TABLE webtry (
code TEXT DEFAULT '' NOT NULL,
create_ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
hash CHAR(64) DEFAULT '' NOT NULL,
source_image_id INTEGER DEFAULT 0 NOT NULL,
PRIMARY KEY(hash)
)`
_, err = db.Exec(sql)
log.Printf("Info: status creating sqlite table for webtry: %q\n", err)
sql = `CREATE TABLE workspace (
name CHAR(64) DEFAULT '' NOT NULL,
create_ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
@ -236,13 +255,15 @@ func init() {
)`
_, err = db.Exec(sql)
log.Printf("Info: status creating sqlite table for workspace: %q\n", err)
sql = `CREATE TABLE workspacetry (
name CHAR(64) DEFAULT '' NOT NULL,
create_ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
hash CHAR(64) DEFAULT '' NOT NULL,
hidden INTEGER DEFAULT 0 NOT NULL,
FOREIGN KEY (name) REFERENCES workspace(name)
sql = `CREATE TABLE workspacetry (
name CHAR(64) DEFAULT '' NOT NULL,
create_ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
hash CHAR(64) DEFAULT '' NOT NULL,
hidden INTEGER DEFAULT 0 NOT NULL,
source_image_id INTEGER DEFAULT 0 NOT NULL,
FOREIGN KEY (name) REFERENCES workspace(name)
)`
_, err = db.Exec(sql)
log.Printf("Info: status creating sqlite table for workspace try: %q\n", err)
@ -258,6 +279,34 @@ func init() {
}
}()
writeOutAllSourceImages()
}
func writeOutAllSourceImages() {
// Pull all the source images from the db and write them out to inout.
rows, err := db.Query("SELECT id, image, create_ts FROM source_images ORDER BY create_ts DESC")
if err != nil {
log.Printf("ERROR: Failed to open connection to SQL server: %q\n", err)
panic(err)
}
for rows.Next() {
var id int
var image []byte
var create_ts time.Time
if err := rows.Scan(&id, &image, &create_ts); err != nil {
log.Printf("Error: failed to fetch from database: %q", err)
continue
}
filename := fmt.Sprintf("../../../inout/image-%d.png", id)
if _, err := os.Stat(filename); os.IsExist(err) {
log.Printf("Skipping write since file exists: %q", filename)
continue
}
if err := ioutil.WriteFile(filename, image, 0666); err != nil {
log.Printf("Error: failed to write image file: %q", err)
}
}
}
// Titlebar is used in titlebar template expansion.
@ -270,6 +319,7 @@ type Titlebar struct {
type userCode struct {
Code string
Hash string
Source int
Titlebar Titlebar
}
@ -283,10 +333,11 @@ func expandToFile(filename string, code string, t *template.Template) error {
return t.Execute(f, userCode{Code: code, Titlebar: Titlebar{GitHash: gitHash, GitInfo: gitInfo}})
}
// expandCode expands the template into a file and calculate the MD5 hash.
func expandCode(code string) (string, error) {
// expandCode expands the template into a file and calculates the MD5 hash.
func expandCode(code string, source int) (string, error) {
h := md5.New()
h.Write([]byte(code))
binary.Write(h, binary.LittleEndian, int64(source))
hash := fmt.Sprintf("%x", h.Sum(nil))
// At this point we are running in skia/experimental/webtry, making cache a
// peer directory to skia.
@ -360,20 +411,96 @@ func reportTryError(w http.ResponseWriter, r *http.Request, err error, message,
w.Write(resp)
}
func writeToDatabase(hash string, code string, workspaceName string) {
func writeToDatabase(hash string, code string, workspaceName string, source int) {
if db == nil {
return
}
if _, err := db.Exec("INSERT INTO webtry (code, hash) VALUES(?, ?)", code, hash); err != nil {
if _, err := db.Exec("INSERT INTO webtry (code, hash, source_image_id) VALUES(?, ?, ?)", code, hash, source); err != nil {
log.Printf("ERROR: Failed to insert code into database: %q\n", err)
}
if workspaceName != "" {
if _, err := db.Exec("INSERT INTO workspacetry (name, hash) VALUES(?, ?)", workspaceName, hash); err != nil {
if _, err := db.Exec("INSERT INTO workspacetry (name, hash, source_image_id) VALUES(?, ?, ?)", workspaceName, hash, source); err != nil {
log.Printf("ERROR: Failed to insert into workspacetry table: %q\n", err)
}
}
}
type Sources struct {
Id int `json:"id"`
}
// sourcesHandler serves up the PNG of a specific try.
func sourcesHandler(w http.ResponseWriter, r *http.Request) {
log.Printf("Sources Handler: %q\n", r.URL.Path)
if r.Method == "GET" {
rows, err := db.Query("SELECT id, create_ts FROM source_images WHERE hidden=0 ORDER BY create_ts DESC")
if err != nil {
http.Error(w, fmt.Sprintf("Failed to query sources: %s.", err), 500)
}
sources := make([]Sources, 0, 0)
for rows.Next() {
var id int
var create_ts time.Time
if err := rows.Scan(&id, &create_ts); err != nil {
log.Printf("Error: failed to fetch from database: %q", err)
continue
}
sources = append(sources, Sources{Id: id})
}
resp, err := json.Marshal(sources)
if err != nil {
reportError(w, r, err, "Failed to serialize a response.")
return
}
w.Header().Set("Content-Type", "application/json")
w.Write(resp)
} else if r.Method == "POST" {
if err := r.ParseMultipartForm(1000000); err != nil {
http.Error(w, fmt.Sprintf("Failed to load image: %s.", err), 500)
return
}
if _, ok := r.MultipartForm.File["upload"]; !ok {
http.Error(w, "Invalid upload.", 500)
return
}
if len(r.MultipartForm.File["upload"]) != 1 {
http.Error(w, "Wrong number of uploads.", 500)
return
}
f, err := r.MultipartForm.File["upload"][0].Open()
if err != nil {
http.Error(w, fmt.Sprintf("Failed to load image: %s.", err), 500)
return
}
defer f.Close()
m, _, err := image.Decode(f)
if err != nil {
http.Error(w, fmt.Sprintf("Failed to decode image: %s.", err), 500)
return
}
var b bytes.Buffer
png.Encode(&b, m)
bounds := m.Bounds()
width := bounds.Max.Y - bounds.Min.Y
height := bounds.Max.X - bounds.Min.X
if _, err := db.Exec("INSERT INTO source_images (image, width, height) VALUES(?, ?, ?)", b.Bytes(), width, height); err != nil {
log.Printf("ERROR: Failed to insert sources into database: %q\n", err)
http.Error(w, fmt.Sprintf("Failed to store image: %s.", err), 500)
return
}
go writeOutAllSourceImages()
// Now redirect back to where we came from.
http.Redirect(w, r, r.Referer(), 302)
} else {
http.NotFound(w, r)
return
}
}
// imageHandler serves up the PNG of a specific try.
func imageHandler(w http.ResponseWriter, r *http.Request) {
log.Printf("Image Handler: %q\n", r.URL.Path)
@ -393,6 +520,7 @@ func imageHandler(w http.ResponseWriter, r *http.Request) {
type Try struct {
Hash string `json:"hash"`
Source int
CreateTS string `json:"create_ts"`
}
@ -431,6 +559,7 @@ type Workspace struct {
Name string
Code string
Hash string
Source int
Tries []Try
Titlebar Titlebar
}
@ -452,13 +581,14 @@ func newWorkspace() (string, error) {
}
// getCode returns the code for a given hash, or the empty string if not found.
func getCode(hash string) (string, error) {
func getCode(hash string) (string, int, error) {
code := ""
if err := db.QueryRow("SELECT code FROM webtry WHERE hash=?", hash).Scan(&code); err != nil {
source := 0
if err := db.QueryRow("SELECT code, source_image_id FROM webtry WHERE hash=?", hash).Scan(&code, &source); err != nil {
log.Printf("ERROR: Code for hash is missing: %q\n", err)
return code, err
return code, source, err
}
return code, nil
return code, source, nil
}
func workspaceHandler(w http.ResponseWriter, r *http.Request) {
@ -469,7 +599,7 @@ func workspaceHandler(w http.ResponseWriter, r *http.Request) {
name := ""
if len(match) == 2 {
name = match[1]
rows, err := db.Query("SELECT create_ts, hash FROM workspacetry WHERE name=? ORDER BY create_ts", name)
rows, err := db.Query("SELECT create_ts, hash, source_image_id FROM workspacetry WHERE name=? ORDER BY create_ts", name)
if err != nil {
reportError(w, r, err, "Failed to select.")
return
@ -477,23 +607,25 @@ func workspaceHandler(w http.ResponseWriter, r *http.Request) {
for rows.Next() {
var hash string
var create_ts time.Time
if err := rows.Scan(&create_ts, &hash); err != nil {
var source int
if err := rows.Scan(&create_ts, &hash, &source); err != nil {
log.Printf("Error: failed to fetch from database: %q", err)
continue
}
tries = append(tries, Try{Hash: hash, CreateTS: create_ts.Format("2006-02-01")})
tries = append(tries, Try{Hash: hash, Source: source, CreateTS: create_ts.Format("2006-02-01")})
}
}
var code string
var hash string
source := 0
if len(tries) == 0 {
code = DEFAULT_SAMPLE
} else {
hash = tries[len(tries)-1].Hash
code, _ = getCode(hash)
code, source, _ = getCode(hash)
}
w.Header().Set("Content-Type", "text/html")
if err := workspaceTemplate.Execute(w, Workspace{Tries: tries, Code: code, Name: name, Hash: hash, Titlebar: Titlebar{GitHash: gitHash, GitInfo: gitInfo}}); err != nil {
if err := workspaceTemplate.Execute(w, Workspace{Tries: tries, Code: code, Name: name, Hash: hash, Source: source, Titlebar: Titlebar{GitHash: gitHash, GitInfo: gitInfo}}); err != nil {
log.Printf("ERROR: Failed to expand template: %q\n", err)
}
} else if r.Method == "POST" {
@ -518,8 +650,9 @@ func hasPreProcessor(code string) bool {
}
type TryRequest struct {
Code string `json:"code"`
Name string `json:"name"` // Optional name of the workspace the code is in.
Code string `json:"code"`
Name string `json:"name"` // Optional name of the workspace the code is in.
Source int `json:"source"` // ID of the source image, 0 if none.
}
// iframeHandler handles the GET and POST of the main page.
@ -540,21 +673,22 @@ func iframeHandler(w http.ResponseWriter, r *http.Request) {
return
}
var code string
code, err := getCode(hash)
code, source, err := getCode(hash)
if err != nil {
http.NotFound(w, r)
return
}
// Expand the template.
w.Header().Set("Content-Type", "text/html")
if err := iframeTemplate.Execute(w, userCode{Code: code, Hash: hash}); err != nil {
if err := iframeTemplate.Execute(w, userCode{Code: code, Hash: hash, Source: source}); err != nil {
log.Printf("ERROR: Failed to expand template: %q\n", err)
}
}
type TryInfo struct {
Hash string `json:"hash"`
Code string `json:"code"`
Hash string `json:"hash"`
Code string `json:"code"`
Source int `json:"source"`
}
// tryInfoHandler returns information about a specific try.
@ -570,14 +704,15 @@ func tryInfoHandler(w http.ResponseWriter, r *http.Request) {
return
}
hash := match[1]
code, err := getCode(hash)
code, source, err := getCode(hash)
if err != nil {
http.NotFound(w, r)
return
}
m := TryInfo{
Hash: hash,
Code: code,
Hash: hash,
Code: code,
Source: source,
}
resp, err := json.Marshal(m)
if err != nil {
@ -599,6 +734,7 @@ func mainHandler(w http.ResponseWriter, r *http.Request) {
log.Printf("Main Handler: %q\n", r.URL.Path)
if r.Method == "GET" {
code := DEFAULT_SAMPLE
source := 0
match := directLink.FindStringSubmatch(r.URL.Path)
var hash string
if len(match) == 2 && r.URL.Path != "/" {
@ -608,14 +744,14 @@ func mainHandler(w http.ResponseWriter, r *http.Request) {
return
}
// Update 'code' with the code found in the database.
if err := db.QueryRow("SELECT code FROM webtry WHERE hash=?", hash).Scan(&code); err != nil {
if err := db.QueryRow("SELECT code, source_image_id FROM webtry WHERE hash=?", hash).Scan(&code, &source); err != nil {
http.NotFound(w, r)
return
}
}
// Expand the template.
w.Header().Set("Content-Type", "text/html")
if err := indexTemplate.Execute(w, userCode{Code: code, Hash: hash, Titlebar: Titlebar{GitHash: gitHash, GitInfo: gitInfo}}); err != nil {
if err := indexTemplate.Execute(w, userCode{Code: code, Hash: hash, Source: source, Titlebar: Titlebar{GitHash: gitHash, GitInfo: gitInfo}}); err != nil {
log.Printf("ERROR: Failed to expand template: %q\n", err)
}
} else if r.Method == "POST" {
@ -641,12 +777,12 @@ func mainHandler(w http.ResponseWriter, r *http.Request) {
reportTryError(w, r, err, "Preprocessor macros aren't allowed.", "")
return
}
hash, err := expandCode(LineNumbers(request.Code))
hash, err := expandCode(LineNumbers(request.Code), request.Source)
if err != nil {
reportTryError(w, r, err, "Failed to write the code to compile.", hash)
return
}
writeToDatabase(hash, request.Code, request.Name)
writeToDatabase(hash, request.Code, request.Name, request.Source)
message, err := doCmd(fmt.Sprintf(RESULT_COMPILE, hash, hash), true)
if err != nil {
message = cleanCompileOutput(message, hash)
@ -661,6 +797,9 @@ func mainHandler(w http.ResponseWriter, r *http.Request) {
}
message += linkMessage
cmd := hash + " --out " + hash + ".png"
if request.Source > 0 {
cmd += fmt.Sprintf(" --source image-%d.png", request.Source)
}
if *useChroot {
cmd = "schroot -c webtry --directory=/inout -- /inout/" + cmd
} else {
@ -706,6 +845,7 @@ func main() {
http.HandleFunc("/recent/", autogzip.HandleFunc(recentHandler))
http.HandleFunc("/iframe/", autogzip.HandleFunc(iframeHandler))
http.HandleFunc("/json/", autogzip.HandleFunc(tryInfoHandler))
http.HandleFunc("/sources/", autogzip.HandleFunc(sourcesHandler))
// Resources are served directly
// TODO add support for caching/etags/gzip