diff --git a/lib/go/camli/search/handler.go b/lib/go/camli/search/handler.go
index f86c19839..91f43ea68 100644
--- a/lib/go/camli/search/handler.go
+++ b/lib/go/camli/search/handler.go
@@ -143,18 +143,19 @@ func (sh *searchHandler) serveDescribe(rw http.ResponseWriter, req *http.Request
}
mime, size, err := sh.index.GetBlobMimeType(br)
- if err != nil {
- // TODO: special error value for not found
- ret["errorText"] = err.String()
- } else {
- m := dmap(br)
- setMimeType(m, mime)
- m["size"] = size
+ if err != os.ENOENT {
+ if err != nil {
+ ret["errorText"] = err.String()
+ } else {
+ m := dmap(br)
+ setMimeType(m, mime)
+ m["size"] = size
- if mime == "application/json; camliType=permanode" {
- pm := make(jsonMap)
- m["permanode"] = pm
- sh.populatePermanodeFields(pm, br, sh.owner, dmap)
+ if mime == "application/json; camliType=permanode" {
+ pm := make(jsonMap)
+ m["permanode"] = pm
+ sh.populatePermanodeFields(pm, br, sh.owner, dmap)
+ }
}
}
diff --git a/server/go/camlistored/ui.go b/server/go/camlistored/ui.go
index 8c7acf988..2a5a6987e 100644
--- a/server/go/camlistored/ui.go
+++ b/server/go/camlistored/ui.go
@@ -133,6 +133,10 @@ func wantsPermanode(req *http.Request) bool {
return req.Method == "GET" && blobref.Parse(req.FormValue("p")) != nil
}
+func wantsBlobInfo(req *http.Request) bool {
+ return req.Method == "GET" && blobref.Parse(req.FormValue("b")) != nil
+}
+
func (ui *UIHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
base := req.Header.Get("X-PrefixHandler-PathBase")
suffix := req.Header.Get("X-PrefixHandler-PathSuffix")
@@ -147,13 +151,18 @@ func (ui *UIHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
file := ""
if m := staticFilePattern.FindStringSubmatch(suffix); m != nil {
file = m[1]
- } else if wantsPermanode(req) {
- file = "permanode.html"
- } else if req.URL.Path == base {
- file = "index.html"
} else {
- http.Error(rw, "Illegal URL.", 404)
- return
+ switch {
+ case wantsPermanode(req):
+ file = "permanode.html"
+ case wantsBlobInfo(req):
+ file = "blobinfo.html"
+ case req.URL.Path == base:
+ file = "index.html"
+ default:
+ http.Error(rw, "Illegal URL.", 404)
+ return
+ }
}
http.ServeFile(rw, req, filepath.Join(ui.FilesDir, file))
}
diff --git a/server/go/camlistored/ui/blobinfo.html b/server/go/camlistored/ui/blobinfo.html
new file mode 100644
index 000000000..99741670a
--- /dev/null
+++ b/server/go/camlistored/ui/blobinfo.html
@@ -0,0 +1,16 @@
+
+
+ Blob
+
+
+
+
+
+ Blob Search Metadata
+
+
+ Blob Contents
+
+
+
+
diff --git a/server/go/camlistored/ui/blobinfo.js b/server/go/camlistored/ui/blobinfo.js
new file mode 100644
index 000000000..05b3459d0
--- /dev/null
+++ b/server/go/camlistored/ui/blobinfo.js
@@ -0,0 +1,74 @@
+/*
+Copyright 2011 Google Inc.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// Gets the |p| query parameter, assuming that it looks like a blobref.
+function getBlobParam() {
+ var blobRef = getQueryParam('b');
+ return (blobRef && isPlausibleBlobRef(blobRef)) ? blobRef : null;
+}
+
+function blobInfoUpdate(bmap) {
+ var blobpre = document.getElementById('blobpre');
+ var blobref = getBlobParam();
+ if (!blobref) {
+ alert("no blobref?");
+ return;
+ }
+ var binfo = bmap[blobref];
+ if (!binfo) {
+ blobpre.innerHTML = "(not found)";
+ return;
+ }
+ blobpre.innerHTML = JSON.stringify(binfo, null, 2);
+ if (binfo.camliType) {
+ camliGetBlobContents(
+ blobref,
+ {
+ success: function(data) {
+ document.getElementById("blobdata").innerHTML = linkifyBlobRefs(data);
+ },
+ fail: alert
+ });
+ } else {
+ document.getElementById("blobdata").innerHTML = "Unknown/binary data; download";
+ }
+}
+
+function linkifyBlobRefs(schemaBlob) {
+ var re = /(\w{3,6}-[a-f0-9]{30,})/g;
+ return schemaBlob.replace(re, "$1");
+}
+
+function blobInfoOnLoad() {
+ var blobref = getBlobParam();
+ if (!blobref) {
+ return
+ }
+ var blobpre = document.getElementById('blobpre');
+ blobpre.innerText = "(loading)";
+ camliDescribeBlob(
+ blobref,
+ {
+ success: blobInfoUpdate,
+ fail: function(msg) {
+ alert("Error describing blob " + blobref + ": " + msg);
+ }
+ }
+ );
+}
+
+window.addEventListener("load", blobInfoOnLoad);
+
\ No newline at end of file
diff --git a/server/go/camlistored/ui/camli.js b/server/go/camlistored/ui/camli.js
index e7333c852..3a71b808f 100644
--- a/server/go/camlistored/ui/camli.js
+++ b/server/go/camlistored/ui/camli.js
@@ -60,6 +60,45 @@ function camliSigDiscovery(opts) {
xhr.send();
}
+function camliDescribeBlob(blobref, opts) {
+ opts = saneOpts(opts);
+ var xhr = new XMLHttpRequest();
+ xhr.onreadystatechange = function() {
+ if (xhr.readyState != 4) { return; }
+ if (xhr.status != 200) {
+ opts.fail("got HTTP status " + xhr.status);
+ return;
+ }
+ var jres;
+ try {
+ jres = JSON.parse(xhr.responseText);
+ } catch (x) {
+ opts.fail("JSON parse error");
+ return;
+ }
+ opts.success(jres);
+ };
+ var path = disco.searchRoot + "camli/search/describe?blobref=" + blobref;
+ xhr.open("GET", path, true);
+ xhr.send();
+}
+
+function camliGetBlobContents(blobref, opts) {
+ opts = saneOpts(opts);
+ var xhr = new XMLHttpRequest();
+ xhr.onreadystatechange = function() {
+ if (xhr.readyState != 4) { return; }
+ if (xhr.status != 200) {
+ opts.fail("got HTTP status " + xhr.status);
+ return;
+ }
+ opts.success(xhr.responseText);
+ };
+ var path = disco.blobRoot + "camli/" + blobref;
+ xhr.open("GET", path, true);
+ xhr.send();
+}
+
function camliSign(clearObj, opts) {
opts = saneOpts(opts);
@@ -160,3 +199,19 @@ function camliCreateNewPermanode(opts) {
});
}
+// Returns the first value from the query string corresponding to |key|.
+// Returns null if the key isn't present.
+function getQueryParam(key) {
+ var params = document.location.search.substring(1).split('&');
+ for (var i = 0; i < params.length; ++i) {
+ var parts = params[i].split('=');
+ if (parts.length == 2 && decodeURIComponent(parts[0]) == key)
+ return decodeURIComponent(parts[1]);
+ }
+ return null;
+}
+
+// Returns true if the passed-in string might be a blobref.
+function isPlausibleBlobRef(blobRef) {
+ return /^\w+-[a-f0-9]+$/.test(blobRef);
+}
diff --git a/server/go/camlistored/ui/permanode.js b/server/go/camlistored/ui/permanode.js
index fe42db26e..c7403c373 100644
--- a/server/go/camlistored/ui/permanode.js
+++ b/server/go/camlistored/ui/permanode.js
@@ -14,27 +14,10 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-// Returns the first value from the query string corresponding to |key|.
-// Returns null if the key isn't present.
-function getQueryParam(key) {
- var params = document.location.search.substring(1).split('&');
- for (var i = 0; i < params.length; ++i) {
- var parts = params[i].split('=');
- if (parts.length == 2 && decodeURIComponent(parts[0]) == key)
- return decodeURIComponent(parts[1]);
- }
- return null;
-}
-
-// Returns true if the passed-in string might be a blobref.
-function isPlausibleBlobRef(blobRef) {
- return /^\w+-[a-f0-9]+$/.test(blobRef);
-}
-
// Gets the |p| query parameter, assuming that it looks like a blobref.
function getPermanodeParam() {
- var blobRef = getQueryParam('p');
- return (blobRef && isPlausibleBlobRef(blobRef)) ? blobRef : null;
+ var blobRef = getQueryParam('p');
+ return (blobRef && isPlausibleBlobRef(blobRef)) ? blobRef : null;
}
window.addEventListener("load", function (e) {