diff --git a/api/sda/sda.go b/api/sda/sda.go index c86731f..2982ffc 100644 --- a/api/sda/sda.go +++ b/api/sda/sda.go @@ -207,7 +207,7 @@ func Download(c *gin.Context) { } // Stitch file and prepare it for streaming - fileStream, err := stitchFile(fileDetails.Header, file, coordinates) + fileStream, err := stitchFile(fileDetails.Header, file) if err != nil { log.Errorf("could not prepare file for streaming, %s", err) c.String(http.StatusInternalServerError, "file stream error") @@ -215,22 +215,98 @@ func Download(c *gin.Context) { return } - sendStream(c.Writer, fileStream) + // Get query params + qStart := r.URL.Query().Get("startCoordinate") + qEnd := r.URL.Query().Get("endCoordinate") + + // Parse and verify coordinates are valid + if len(qStart) > 0 && len(qEnd) > 0 { + start, err := strconv.ParseUint(qStart, 10, 64) + if err != nil { + log.Errorf("failed to convert start coordinate %d to integer, %s", start, err) + + return nil, errors.New("startCoordinate must be an integer") + } + end, err := strconv.ParseUint(qEnd, 10, 64) + if err != nil { + log.Errorf("failed to convert end coordinate %d to integer, %s", end, err) + + return nil, errors.New("endCoordinate must be an integer") + } + if end < start { + log.Errorf("endCoordinate=%d must be greater than startCoordinate=%d", end, start) + + return nil, errors.New("endCoordinate must be greater than startCoordinate") + } + + if start != 0 { + // We don't want to read from start, skip ahead to where we should be + if _, err := reader.Seek(start, 0); err != nil { + return err + } + } + + // Calculate how much we should read (if given) + togo := end - start + + buf := make([]byte, 4096) + + // Loop until we've read what we should (if no/faulty end given, that's EOF) + for end == 0 || togo > 0 { + rbuf := buf[:] + + if end != 0 && togo < 4096 { + // If we don't want to read as much as 4096 bytes + rbuf = buf[:togo] + } + r, err := fileStream.Read(rbuf) + togo -= int64(r) + + // Nothing more to read? + if err == io.EOF && r == 0 { + // Fall out without error if we had EOF (if we got any data, do one + // more lap in the loop) + return nil + } + + if err != nil && err != io.EOF { + // An error we want to signal? + return err + } + + wbuf := rbuf[:r] + for len(wbuf) > 0 { + // Loop until we've written all that we could read, + // fall out on error + sendStream(c.Writer, wbuf) + + if err != nil { + return err + } + wbuf = wbuf[w:] + } + } + + } else { + return + } + } // stitchFile stitches the header and file body together for Crypt4GHReader // and returns a streamable Reader -var stitchFile = func(header []byte, file io.ReadCloser, coordinates *headers.DataEditListHeaderPacket) (*streaming.Crypt4GHReader, error) { +var stitchFile = func(header []byte, file io.ReadCloser) (*streaming.Crypt4GHReader, error) { log.Debugf("stitching header to file %s for streaming", file) // Stitch header and file body together hr := bytes.NewReader(header) mr := io.MultiReader(hr, file) - c4ghr, err := streaming.NewCrypt4GHReader(mr, *config.Config.App.Crypt4GHKey, coordinates) + c4ghr, err := streaming.NewCrypt4GHReader(mr, *config.Config.App.Crypt4GHKey, nil) if err != nil { log.Errorf("failed to create Crypt4GH stream reader, %v", err) return nil, err } + defer c4ghr.Close() log.Debugf("file stream for %s constructed", file) return c4ghr, nil