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 +} +