aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJarkko Pöyry <jpoyry@google.com>2015-04-07 13:31:45 -0700
committerJarkko Poyry <jpoyry@google.com>2015-04-10 00:45:11 +0000
commitd17ce40133e19edd03bd71de4f1f3ea70f8f3227 (patch)
treeafe08513a72985a4bfa08df82055916ff9de42f1
parent25092e56aded73d3ed0b76b86c9d37c8ebba8c1f (diff)
downloadcherry-d17ce40133e19edd03bd71de4f1f3ea70f8f3227.tar.gz
Support multifile import.
Change-Id: I21c130f1f68f2d7c6d4886f491fc94fd30f72a12
-rw-r--r--cherry/testrunner.go24
-rw-r--r--client/partials/results.html2
-rw-r--r--server.go49
3 files changed, 61 insertions, 14 deletions
diff --git a/cherry/testrunner.go b/cherry/testrunner.go
index e4a8a4b..ce80e59 100644
--- a/cherry/testrunner.go
+++ b/cherry/testrunner.go
@@ -23,9 +23,9 @@ import (
"strings"
"time"
"fmt"
+ "mime/multipart"
"unicode/utf8"
"strconv"
- "io"
"bufio"
"bytes"
"os"
@@ -735,7 +735,7 @@ func (runner *TestRunner) QueryBatchExecutionLog (batchResultId string) (string,
return <-executionLogChan, nil
}
-func (runner *TestRunner) ImportBatch (batchResultId string, batchResultDefaultName string, qpaReader io.ReadCloser, totalInputLength int64) error {
+func (runner *TestRunner) ImportBatch (batchResultId string, batchResultDefaultName string, qpaReader *multipart.Part, totalContentLength int64) error {
var stopRequest <-chan struct{}
{
stopRequestBidir := make(chan struct{})
@@ -780,16 +780,14 @@ func (runner *TestRunner) ImportBatch (batchResultId string, batchResultDefaultN
{
opSet := rtdb.NewOpSet()
keepReading := true
+ eofReached := false
lastDbExecReadCount := int64(0) // The value of countingQpaReader.Count() when the latest write to DB was done.
existingTreeNodePaths := make(map[string]struct{})
existingTreeNodePaths[""] = struct{}{} // \note Root node was already added above.
- for keepReading && scanner.Scan() {
- qpaParser.ParseLine(scanner.Text())
-
+ for keepReading {
select {
case <-stopRequest:
- qpaReader.Close()
keepReading = false
case event := <-qpaParserQueue:
@@ -862,12 +860,24 @@ func (runner *TestRunner) ImportBatch (batchResultId string, batchResultDefaultN
}
default:
+ if (eofReached) {
+ keepReading = false
+ } else if scanner.Scan() {
+ qpaParser.ParseLine(scanner.Text())
+ } else {
+ // EOF reached
+ qpaParser.Terminate()
+ eofReached = true
+ }
}
// \note Write to DB in chunks.
readCount := countingQpaReader.Count()
if readCount - lastDbExecReadCount >= 4*1024*1024 {
- opSet.Call(typeBatchResult, batchResultId, "SetInitProgress", float32(float64(readCount) / float64(totalInputLength)))
+ if totalContentLength != -1 { // Content-Length = -1 mean unknown
+ approximateProgress := float32(float64(readCount) / float64(totalContentLength))
+ opSet.Call(typeBatchResult, batchResultId, "SetInitProgress", approximateProgress)
+ }
err := runner.rtdbServer.ExecuteOpSet(opSet)
if err != nil { panic(err) }
opSet = rtdb.NewOpSet()
diff --git a/client/partials/results.html b/client/partials/results.html
index 7dd3b0d..9b9a17e 100644
--- a/client/partials/results.html
+++ b/client/partials/results.html
@@ -23,7 +23,7 @@ limitations under the License.
<form enctype="multipart/form-data" ng-controller="BatchImportCtrl" class="pull-left">
<!-- \note Hacky way of "customizing" the file input button: actual input is invisible, contained inside a span visualized as a button. -->
<span class="btn btn-default file-input-button">
- Import existing batch <input type="file" name="file" file-name-model="filename"> <!-- \note AngularJS doesn't support ng-model on file inputs. -->
+ Import existing batch <input type="file" name="file" file-name-model="filename" multiple> <!-- \note AngularJS doesn't support ng-model on file inputs. -->
</span>
</form>
<a class="btn btn-default pull-right" ng-disabled="selectedBatchResults.length === 0" ng-click="$event.stopPropagation()" ui-sref="batchResultComparison({batchResultIds:comparisonBatchIdsStr()})">Compare selected</a>
diff --git a/server.go b/server.go
index 639b34f..ca9b115 100644
--- a/server.go
+++ b/server.go
@@ -250,13 +250,50 @@ func importHandler (response http.ResponseWriter, request *http.Request) {
return
}
+ parts, err := request.MultipartReader()
+ if err != nil {
+ http.Error(response, "Expected a multipart upload", 400)
+ return
+ }
+
log.Printf("[import] Received request with Content-Length %d\n", request.ContentLength)
- startTime := time.Now()
- batchResultId := startTime.Format(time.RFC3339)
- batchResultDefaultName := startTime.Format("2006-Jan-02 15:04:05")
- testRunner.ImportBatch(batchResultId, batchResultDefaultName, request.Body, request.ContentLength)
- response.WriteHeader(http.StatusOK)
- log.Printf("[import] Finished\n")
+
+ anyImportSucceeded := false
+ anyImportFailed := false
+
+ for {
+ file, err := parts.NextPart();
+ if err != nil {
+ break
+ }
+
+ startTime := time.Now()
+ batchResultId := startTime.Format(time.RFC3339)
+ batchResultDefaultName := startTime.Format("2006-Jan-02 15:04:05")
+ err = testRunner.ImportBatch(batchResultId, batchResultDefaultName, file, request.ContentLength)
+ if err != nil {
+ log.Printf("[import] Import failed with error %v\n", err)
+ anyImportFailed = true
+ } else {
+ log.Printf("[import] Single import finished\n")
+ anyImportSucceeded = true
+ }
+
+ file.Close()
+ }
+
+ request.Body.Close()
+
+ if !anyImportFailed {
+ // no failures
+ response.WriteHeader(http.StatusOK)
+ } else if !anyImportSucceeded {
+ // no successes
+ http.Error(response, "Import failed", 500)
+ } else {
+ // both failures and successes
+ http.Error(response, "Partial failure", 207)
+ }
}
// Mapping of third party locations to desired server locations