diff options
author | Jarkko Pöyry <jpoyry@google.com> | 2015-04-07 13:31:45 -0700 |
---|---|---|
committer | Jarkko Poyry <jpoyry@google.com> | 2015-04-10 00:45:11 +0000 |
commit | d17ce40133e19edd03bd71de4f1f3ea70f8f3227 (patch) | |
tree | afe08513a72985a4bfa08df82055916ff9de42f1 | |
parent | 25092e56aded73d3ed0b76b86c9d37c8ebba8c1f (diff) | |
download | cherry-d17ce40133e19edd03bd71de4f1f3ea70f8f3227.tar.gz |
Support multifile import.
Change-Id: I21c130f1f68f2d7c6d4886f491fc94fd30f72a12
-rw-r--r-- | cherry/testrunner.go | 24 | ||||
-rw-r--r-- | client/partials/results.html | 2 | ||||
-rw-r--r-- | server.go | 49 |
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> @@ -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 |