From e648f80e31804e9cf33e865b4621d26ff7f4dfe7 Mon Sep 17 00:00:00 2001 From: Kleissner Date: Mon, 29 Nov 2021 17:51:22 +0100 Subject: [PATCH] webapi: /file/read and /file/view serve file from warehouse if available. --- webapi/File IO.go | 58 +++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 49 insertions(+), 9 deletions(-) diff --git a/webapi/File IO.go b/webapi/File IO.go index 5f5b3b4..8b8b303 100644 --- a/webapi/File IO.go +++ b/webapi/File IO.go @@ -16,6 +16,7 @@ import ( "github.com/PeernetOfficial/core" "github.com/PeernetOfficial/core/btcec" "github.com/PeernetOfficial/core/protocol" + "github.com/PeernetOfficial/core/warehouse" ) /* @@ -67,6 +68,11 @@ func apiFileRead(w http.ResponseWriter, r *http.Request) { offset = ranges[0].start } + // Is the file available in the local warehouse? In that case requesting it from the remote is unnecessary. + if serveFileFromWarehouse(w, fileHash, uint64(offset), uint64(limit), ranges) { + return + } + // try connecting via node ID or peer ID? var peer *core.PeerInfo @@ -97,6 +103,35 @@ func apiFileRead(w http.ResponseWriter, r *http.Request) { io.Copy(w, io.LimitReader(reader, int64(transferSize))) } +// serveFileFromWarehouse serves the file from the warehouse. If it is not available, it returns false and does not use the writer. +// Limit is optional, 0 means the entire file. +func serveFileFromWarehouse(w http.ResponseWriter, fileHash []byte, offset, limit uint64, ranges []HTTPRange) (valid bool) { + // Check if the file is available in the local warehouse. + _, fileInfo, status, _ := core.UserWarehouse.FileExists(fileHash) + if status != warehouse.StatusOK { + return false + } + fileSize := uint64(fileInfo.Size()) + + // validate offset and limit + if limit > 0 && offset+limit > fileSize { + http.Error(w, "invalid limit", http.StatusBadRequest) + return true + } else if offset > fileSize { + http.Error(w, "invalid offset", http.StatusBadRequest) + return true + } else if limit == 0 { + limit = fileSize - offset + } + + setContentLengthRangeHeader(w, offset, limit, uint64(fileInfo.Size()), ranges) + + status, _, _ = core.UserWarehouse.ReadFile(fileHash, int64(offset), int64(limit), w) + + // StatusErrorReadFile must be considered success, since parts of the file may have been transferred already and recovery is not possible. + return status == warehouse.StatusErrorReadFile || status == warehouse.StatusOK +} + /* apiFileView is similar to /file/read but but provides a format parameter. It sets the Content-Type and Accept-Ranges headers. This endpoint supports the Range, Content-Range and Content-Length headers. Multipart ranges are not supported and result in HTTP 400. @@ -148,6 +183,19 @@ func apiFileView(w http.ResponseWriter, r *http.Request) { offset = ranges[0].start } + w.Header().Set("Accept-Ranges", "bytes") // always indicate accepting of Range header + + switch format { + case 14: + // Video: Indicate MP4 always. There are tons of other MIME types that could be used. + w.Header().Set("Content-Type", "video/mp4") + } + + // Is the file available in the local warehouse? In that case requesting it from the remote is unnecessary. + if serveFileFromWarehouse(w, fileHash, uint64(offset), uint64(limit), ranges) { + return + } + // try connecting via node ID or peer ID? var peer *core.PeerInfo @@ -171,14 +219,6 @@ func apiFileView(w http.ResponseWriter, r *http.Request) { return } - w.Header().Set("Accept-Ranges", "bytes") // always indicate accepting of Range header - - switch format { - case 14: - // Video: Indicate MP4 always. There are tons of other MIME types that could be used. - w.Header().Set("Content-Type", "video/mp4") - } - // set the right headers setContentLengthRangeHeader(w, uint64(offset), transferSize, fileSize, ranges) @@ -223,7 +263,7 @@ func PeerConnectNode(nodeID []byte, timeout time.Duration) (peer *core.PeerInfo, } // FileStartReader providers a reader to a remote file. The reader must be closed by the caller. -// File Size is the full file size reported by the remote peer, regardless of the requested offset and limit. +// File Size is the full file size reported by the remote peer, regardless of the requested offset and limit. Limit is optional (0 means the entire file). // Transfer Size is the size in bytes that is actually going to be transferred. The reader should be closed after reading that amount. // The optional cancelChan can be used to stop the file transfer at any point. func FileStartReader(peer *core.PeerInfo, hash []byte, offset, limit uint64, cancelChan <-chan struct{}) (reader io.ReadCloser, fileSize, transferSize uint64, err error) {