diff --git a/lib/go/camli/httputil/httputil.go b/lib/go/camli/httputil/httputil.go index 277170d6b..a0c3926d2 100644 --- a/lib/go/camli/httputil/httputil.go +++ b/lib/go/camli/httputil/httputil.go @@ -45,14 +45,21 @@ func ReturnJson(conn http.ResponseWriter, data interface{}) { conn.Header().Set("Content-Type", "text/javascript") if m, ok := data.(map[string]interface{}); ok { + statusCode := 0 + if t, ok := m["error"].(string); ok && len(t) > 0 { + statusCode = http.StatusInternalServerError + } if t, ok := m["errorType"].(string); ok { switch t { case "server": - conn.WriteHeader(http.StatusInternalServerError) + statusCode = http.StatusInternalServerError case "input": - conn.WriteHeader(http.StatusBadRequest) + statusCode = http.StatusBadRequest } } + if statusCode != 0 { + conn.WriteHeader(statusCode) + } } bytes, err := json.MarshalIndent(data, "", " ") diff --git a/server/go/camlistored/ui.go b/server/go/camlistored/ui.go index 68ad03c95..75d4064d2 100644 --- a/server/go/camlistored/ui.go +++ b/server/go/camlistored/ui.go @@ -17,7 +17,6 @@ limitations under the License. package main import ( - "bytes" "fmt" "http" "io" @@ -207,48 +206,6 @@ func (ui *UIHandler) serveDiscovery(rw http.ResponseWriter, req *http.Request) { } } -func (ui *UIHandler) serveUploadHelper(rw http.ResponseWriter, req *http.Request) { - if ui.Storage == nil { - http.Error(rw, "No BlobRoot configured", 500) - return - } - var buf bytes.Buffer - defer io.Copy(rw, &buf) - - fmt.Fprintf(&buf, "
\n") - - mr, err := req.MultipartReader() - if err != nil { - fmt.Fprintf(&buf, "multipart reader: %v", err) - return - } - - for { - part, err := mr.NextPart() - if err == os.EOF { - break - } - if err != nil { - buf.Reset() - http.Error(rw, "Multipart error: "+err.String(), 500) - break - } - fileName := part.FileName() - if fileName == "" { - continue - } - get, _ := http.ParseQuery(req.URL.RawQuery) - writeFn := schema.WriteFileFromReader - if len(get["rollsum"]) == 1 && get["rollsum"][0] == "1" { - writeFn = schema.WriteFileFromReaderRolling - } - br, err := writeFn(ui.Storage, fileName, part) - - fmt.Fprintf(&buf, "filename=%q, formname=%q, br=%s, err=%v\n", part.FileName(), part.FormName(), br, br, err) - - } -} - func (ui *UIHandler) storageSeekFetcher() (blobref.SeekFetcher, os.Error) { return blobref.SeekerFromStreamingFetcher(ui.Storage) } diff --git a/server/go/camlistored/ui/camli.js b/server/go/camlistored/ui/camli.js index 11f7fa6d6..9394bc34e 100644 --- a/server/go/camlistored/ui/camli.js +++ b/server/go/camlistored/ui/camli.js @@ -63,7 +63,7 @@ function camliSigDiscovery(opts) { xhr.onreadystatechange = function() { if (xhr.readyState != 4) { return; } if (xhr.status != 200) { - opts.fail("no status 200; got " + xhr.status); + opts.fail("camliSigDiscovery expected status 200; got " + xhr.status); return; } sigdisco = JSON.parse(xhr.responseText); @@ -171,6 +171,41 @@ function camliSign(clearObj, opts) { }); } +function camliUploadFileHelper(file, opts) { + opts = saneOpts(opts); + if (!disco.uploadHelper) { + opts.fail("no uploadHelper available"); + return + } + var fd = new FormData(); + fd.append(fd, file); + var xhr = new XMLHttpRequest(); + + xhr.open("POST", disco.uploadHelper); + xhr.onreadystatechange = function() { + if (xhr.readyState != 4) { + return; + } + if (xhr.status != 200) { + opts.fail("got status " + xhr.status); + return; + } + var resj; + try { + resj = JSON.parse(xhr.responseText); + } catch (x) { + opts.fail("error parsing JSON in upload response: " + xhr.responseText); + return; + } + if (resj.error) { + opts.fail("error uploading " + blobref + ": " + resj.error); + return; + } + opts.success(resj); + }; + xhr.send(fd); +} + function camliUploadString(s, opts) { opts = saneOpts(opts); var blobref = "sha1-" + Crypto.SHA1(s); @@ -205,7 +240,7 @@ function camliUploadString(s, opts) { opts.fail("error parsing JSON in upload response: " + xhr.responseText); return; } - if (resj.errorText) { + if (resj.errorText) { // TODO: change this to error, not errorText, to be consistent opts.fail("error uploading " + blobref + ": " + resj.errorText); return; } @@ -257,14 +292,14 @@ function camliGetRecentlyUpdatedPermanodes(opts) { xhr.onreadystatechange = function() { if (xhr.readyState != 4) { return; } if (xhr.status != 200) { - opts.fail("no status 200; got " + xhr.status); + opts.fail("camliGetRecentlyUpdatedPermanodes expected status 200; got " + xhr.status); return; } var resj; try { resj = JSON.parse(xhr.responseText); } catch(x) { - opts.fail("error parsing JSON in upload response: " + xhr.responseText); + opts.fail("error parsing JSON in response: " + xhr.responseText); return } opts.success(resj); @@ -273,6 +308,33 @@ function camliGetRecentlyUpdatedPermanodes(opts) { xhr.send(); } +function camliFindExistingFileSchemas(bytesRef, opts) { + opts = saneOpts(opts); + var xhr = new XMLHttpRequest(); + xhr.onreadystatechange = function() { + if (xhr.readyState != 4) { return; } + if (xhr.status != 200) { + opts.fail("camliFindExistingFileSchemas expected status 200; got " + xhr.status + ", " + xhr.statusText); + return; + } + var resj; + try { + resj = JSON.parse(xhr.responseText); + } catch(x) { + opts.fail("error parsing JSON in response: " + xhr.responseText); + return + } + if (resj.error) { + opts.fail(resj.error); + } else { + opts.success(resj); + } + }; + var path = disco.searchRoot + "camli/search/files?bytesref=" + bytesRef; + xhr.open("GET", path, true); + xhr.send(); +} + // 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.html b/server/go/camlistored/ui/permanode.html index e7062585e..5af1c1055 100644 --- a/server/go/camlistored/ui/permanode.html +++ b/server/go/camlistored/ui/permanode.html @@ -7,6 +7,13 @@ +[Home]diff --git a/server/go/camlistored/ui/permanode.js b/server/go/camlistored/ui/permanode.js index 7173bd1b2..a784b9f4e 100644 --- a/server/go/camlistored/ui/permanode.js +++ b/server/go/camlistored/ui/permanode.js @@ -130,28 +130,55 @@ var lastFiles; function handleFiles(files) { lastFiles = files; - info = document.getElementById("info"); - t = "N files: " + files.length + "\n"; for (var i = 0; i < files.length; i++) { var file = files[i]; - t += "file[" + i + "] name=" + file.name + "; size=" + file.size + "; file.type=" + file.type + "\n"; - - (function(file) { - var fr = new FileReader(); - fr.onload = function() { - dataurl = fr.result; - comma = dataurl.indexOf(",") - if (comma != -1) { - b64 = dataurl.substring(comma + 1); - var arrayBuffer = Base64.decode(b64).buffer; - var hash = Crypto.SHA1(new Uint8Array(arrayBuffer, 0)); - info.innerHTML += "File " + file.name + " = sha1-" + hash + "\n"; - } - }; - fr.readAsDataURL(file); - })(file); + startFileUpload(file); } - info.innerHTML += t; +} + +function startFileUpload(file) { + var dnd = document.getElementById("dnd"); + var up = document.createElement("div"); + up.setAttribute("class", "fileupload"); + dnd.appendChild(up); + var info = "name=" + file.name + " size=" + file.size + "; type=" + file.type; + up.innerHTML = info + " (scanning)"; + var contentsRef; // set later + + var onFail = function(msg) { + up.innerHTML = info + " fail: "; + up.appendChild(document.createTextNode(msg)); + }; + + var onUploaded = function(res) { + alert("Uploaded: " + JSON.stringify(res, null, 2)); + }; + + var onFileSearch = function(res) { + if (res.files.length > 0) { + up.innerHTML = info + " TODO: server dup, handle"; + alert("TODO: server already has it, maybe. verify the files in " + JSON.stringify(res, null, 2)); + return; + } + up.innerHTML = info + " Uploading..."; + camliUploadFileHelper(file, { success: onUploaded, fail: onFail }); + }; + + var fr = new FileReader(); + fr.onload = function() { + dataurl = fr.result; + comma = dataurl.indexOf(",") + if (comma != -1) { + b64 = dataurl.substring(comma + 1); + var arrayBuffer = Base64.decode(b64).buffer; + var hash = Crypto.SHA1(new Uint8Array(arrayBuffer, 0)); + + contentsRef = "sha1-" + hash; + up.innerHTML = info + " (checking for dup of " + contentsRef + ")"; + camliFindExistingFileSchemas(contentsRef, { success: onFileSearch, fail: onFail }); + } + }; + fr.readAsDataURL(file); } function onFileFormSubmit(e) { diff --git a/server/go/camlistored/uploadhelper.go b/server/go/camlistored/uploadhelper.go new file mode 100644 index 000000000..9fa4147ee --- /dev/null +++ b/server/go/camlistored/uploadhelper.go @@ -0,0 +1,80 @@ +/* +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. +*/ + +package main + +import ( + "http" + "os" + + "camli/httputil" + "camli/schema" +) + +func (ui *UIHandler) serveUploadHelper(rw http.ResponseWriter, req *http.Request) { + rollSum := req.URL.Query().Get("rollsum") == "1" + + ret := make(map[string]interface{}) + defer httputil.ReturnJson(rw, ret) + + if ui.Storage == nil { + ret["error"] = "No BlobRoot configured" + ret["errorType"] = "server" + return + } + + mr, err := req.MultipartReader() + if err != nil { + ret["error"] = "reading body: " + err.String() + ret["errorType"] = "server" + return + } + + got := make([]map[string]interface{}, 0) + for { + part, err := mr.NextPart() + if err == os.EOF { + break + } + if err != nil { + ret["error"] = "reading body: " + err.String() + ret["errorType"] = "server" + break + } + fileName := part.FileName() + if fileName == "" { + continue + } + writeFn := schema.WriteFileFromReader + if rollSum { + writeFn = schema.WriteFileFromReaderRolling + } + br, err := writeFn(ui.Storage, fileName, part) + + if err == nil { + got = append(got, map[string]interface{}{ + "filename": part.FileName(), + "formname": part.FormName(), + "fileref": br.String(), + }) + } else { + ret["error"] = "writing to blobserver: " + err.String() + return + } + } + ret["got"] = got +} +