Initial X-Missing work
This commit is contained in:
parent
4f811a8ffe
commit
9c2d2275a8
|
@ -129,15 +129,15 @@ Host: tus.example.com
|
||||||
HTTP/1.1 200 Ok
|
HTTP/1.1 200 Ok
|
||||||
Content-Length: 100
|
Content-Length: 100
|
||||||
Content-Type: image/jpg
|
Content-Type: image/jpg
|
||||||
X-Resume: bytes=20-50,60-99
|
X-Missing: bytes=20-50,60-99
|
||||||
```
|
```
|
||||||
|
|
||||||
The `X-Resume` header holds a [byte
|
The `X-Missing` header holds a [byte
|
||||||
range](http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35.1) that
|
range](http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35.1) that
|
||||||
informs the client which parts of the file have not been received yet. It is
|
informs the client which parts of the file have not been received yet. It is
|
||||||
up to the client to choose appropiate `PUT` requests to complete the upload.
|
up to the client to choose appropiate `PUT` requests to complete the upload.
|
||||||
|
|
||||||
The absence of a `X-Resume` header means that the the entire file has been
|
The absence of a `X-Missing` header means that the the entire file has been
|
||||||
received by the server.
|
received by the server.
|
||||||
|
|
||||||
### GET \<fileUrl\>
|
### GET \<fileUrl\>
|
||||||
|
|
|
@ -6,8 +6,11 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
var dataDir string
|
var dataDir string
|
||||||
|
@ -82,3 +85,61 @@ func putFileChunk(fileId string, start int64, end int64, r io.Reader) error {
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getReceivedChunks(fileId string) (chunkSet, error) {
|
||||||
|
l := logPath(fileId)
|
||||||
|
// @TODO stream the file / limit log file size?
|
||||||
|
data, err := ioutil.ReadFile(l)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
lines := strings.Split(string(data), "\n")
|
||||||
|
|
||||||
|
|
||||||
|
chunks := make(chunkSet, 0, len(lines)-1)
|
||||||
|
for i, line := range lines {
|
||||||
|
// last line is always empty, skip it
|
||||||
|
if lastLine := i+1 == len(lines); lastLine {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := strings.Split(line, ",")
|
||||||
|
if len(parts) != 2 {
|
||||||
|
return nil, errors.New("getReceivedChunks: corrupt log line: "+line)
|
||||||
|
}
|
||||||
|
|
||||||
|
start, err := strconv.ParseInt(parts[0], 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("getReceivedChunks: invalid start: "+parts[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
end, err := strconv.ParseInt(parts[1], 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("getReceivedChunks: invalid end: "+parts[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
chunks.Add(chunk{Start: start, End: end})
|
||||||
|
}
|
||||||
|
|
||||||
|
return chunks, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getMissingChunks(fileId string) (chunkSet, error) {
|
||||||
|
d := dataPath(fileId)
|
||||||
|
stat, err := os.Stat(d)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
receivedChunks, err := getReceivedChunks(fileId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// @TODO actually calcuate missing chunks instead of received
|
||||||
|
_ = stat
|
||||||
|
|
||||||
|
//chunks := chunkSet{{Start: 0, End: stat.Size()-1}}
|
||||||
|
//chunks := make(chunkSet, 0)
|
||||||
|
return receivedChunks, nil
|
||||||
|
}
|
||||||
|
|
|
@ -30,7 +30,7 @@ func route(w http.ResponseWriter, r *http.Request) {
|
||||||
// WIP
|
// WIP
|
||||||
switch r.Method {
|
switch r.Method {
|
||||||
case "HEAD":
|
case "HEAD":
|
||||||
w.Header().Set("X-Resume", "bytes=0-99")
|
headFile(w, r, id)
|
||||||
case "GET":
|
case "GET":
|
||||||
reply(w, http.StatusNotImplemented, "File download")
|
reply(w, http.StatusNotImplemented, "File download")
|
||||||
case "PUT":
|
case "PUT":
|
||||||
|
@ -76,7 +76,7 @@ func postFiles(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// @TODO: Return X-Resume header
|
// @TODO: Return X-Missing header
|
||||||
|
|
||||||
w.Header().Set("Location", "/files/"+id)
|
w.Header().Set("Location", "/files/"+id)
|
||||||
w.WriteHeader(http.StatusCreated)
|
w.WriteHeader(http.StatusCreated)
|
||||||
|
@ -98,5 +98,25 @@ func putFile(w http.ResponseWriter, r *http.Request, fileId string) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// @TODO: Return X-Resume header
|
// @TODO: Return X-Missing header
|
||||||
|
}
|
||||||
|
|
||||||
|
func headFile(w http.ResponseWriter, r *http.Request, fileId string) {
|
||||||
|
chunks, err := getMissingChunks(fileId)
|
||||||
|
if err != nil {
|
||||||
|
reply(w, http.StatusInternalServerError, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
missing := ""
|
||||||
|
for i, chunk := range chunks {
|
||||||
|
missing += fmt.Sprintf("%d-%d", chunk.Start, chunk.End)
|
||||||
|
if i + 1 < len(chunks) {
|
||||||
|
missing += ","
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if missing != "" {
|
||||||
|
w.Header().Set("X-Missing", "bytes="+missing)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue