aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorGravatar commit-bot@chromium.org <commit-bot@chromium.org@2bbb7eff-a529-9590-31e7-b0007b416f81>2014-05-29 15:58:00 +0000
committerGravatar commit-bot@chromium.org <commit-bot@chromium.org@2bbb7eff-a529-9590-31e7-b0007b416f81>2014-05-29 15:58:00 +0000
commit0a7e5b7554b066dd5879395fc114712dd234e643 (patch)
tree5fdefab9b1a260f924942c43ab60c8d34e802f78
parentcba73780bbd12fd254229517aec04fcbf0b64b52 (diff)
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
-rw-r--r--experimental/webtry/DESIGN.md70
-rw-r--r--experimental/webtry/main.cpp12
-rw-r--r--experimental/webtry/res/css/webtry.css32
-rw-r--r--experimental/webtry/res/js/webtry.js144
-rw-r--r--experimental/webtry/result.cpp1
-rw-r--r--experimental/webtry/templates/content.html19
-rw-r--r--experimental/webtry/templates/template.cpp2
-rw-r--r--experimental/webtry/webtry.go214
8 files changed, 422 insertions, 72 deletions
diff --git a/experimental/webtry/DESIGN.md b/experimental/webtry/DESIGN.md
index 0bc2145128..4c0cee277a 100644
--- a/experimental/webtry/DESIGN.md
+++ b/experimental/webtry/DESIGN.md
@@ -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
-----
diff --git a/experimental/webtry/main.cpp b/experimental/webtry/main.cpp
index 7ccb9322f9..44f8aab70f 100644
--- a/experimental/webtry/main.cpp
+++ b/experimental/webtry/main.cpp
@@ -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);
diff --git a/experimental/webtry/res/css/webtry.css b/experimental/webtry/res/css/webtry.css
index 3b04d7dcbf..09751df532 100644
--- a/experimental/webtry/res/css/webtry.css
+++ b/experimental/webtry/res/css/webtry.css
@@ -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%;
diff --git a/experimental/webtry/res/js/webtry.js b/experimental/webtry/res/js/webtry.js
index b1fe3dd806..b24501e143 100644
--- a/experimental/webtry/res/js/webtry.js
+++ b/experimental/webtry/res/js/webtry.js
@@ -18,29 +18,33 @@
*/
(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 editor = CodeMirror.fromTextArea(code, {
- theme: "default",
- lineNumbers: true,
- matchBrackets: true,
- mode: "text/x-c++src",
- indentUnit: 4,
- });
+ 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);
+ }
- // Match the initial textarea size.
- editor.setSize(editor.defaultCharWidth() * code.cols,
- editor.defaultTextHeight() * code.rows);
function beginWait() {
document.body.classList.add('waiting');
@@ -54,6 +58,101 @@
}
+ 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",
+ lineNumbers: true,
+ matchBrackets: true,
+ mode: "text/x-c++src",
+ indentUnit: 4,
+ });
+
+ // Match the initial textarea size.
+ editor.setSize(editor.defaultCharWidth() * code.cols,
+ editor.defaultTextHeight() * code.rows);
+
+
/**
* Callback when there's an XHR error.
* @param e The callback event.
@@ -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);
diff --git a/experimental/webtry/result.cpp b/experimental/webtry/result.cpp
index d06ef9c3da..cc1c2f6516 100644
--- a/experimental/webtry/result.cpp
+++ b/experimental/webtry/result.cpp
@@ -7,6 +7,7 @@
#include "SkStream.h"
#include "SkSurface.h"
+SkBitmap source;
void draw(SkCanvas* canvas) {
#line 1
diff --git a/experimental/webtry/templates/content.html b/experimental/webtry/templates/content.html
index 8638cf2bd1..0f2177a38a 100644
--- a/experimental/webtry/templates/content.html
+++ b/experimental/webtry/templates/content.html
@@ -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}}'/>
diff --git a/experimental/webtry/templates/template.cpp b/experimental/webtry/templates/template.cpp
index 67d2c04dc9..c1f40f30e7 100644
--- a/experimental/webtry/templates/template.cpp
+++ b/experimental/webtry/templates/template.cpp
@@ -158,4 +158,6 @@
#include "SkXfermode.h"
#include "SkXfermodeImageFilter.h"
+SkBitmap source;
+
{{.Code}}
diff --git a/experimental/webtry/webtry.go b/experimental/webtry/webtry.go
index 2c3d7c92e6..19eeb4c501 100644
--- a/experimental/webtry/webtry.go
+++ b/experimental/webtry/webtry.go
@@ -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,
+ 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)
+ 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