Remove old tusd code

This commit is contained in:
Felix Geisendörfer 2013-05-08 16:03:59 +02:00
parent 4c10bea894
commit 9f29ced4ec
3 changed files with 0 additions and 385 deletions

View File

@ -1,74 +0,0 @@
package main
import (
"errors"
"strconv"
"strings"
)
var errInvalidRange = errors.New("invalid Content-Range")
type contentRange struct {
Start int64
End int64
Size int64
}
// parseContentRange parse a Content-Range string like "5-10/100".
// see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.16 .
// "*" values causes End/Size to be set to -1, see test case for more details.
func parseContentRange(s string) (*contentRange, error) {
const prefix = "bytes "
offset := strings.Index(s, prefix)
if offset != 0 {
return nil, errInvalidRange
}
s = s[len(prefix):]
parts := strings.Split(s, "/")
if len(parts) != 2 {
return nil, errInvalidRange
}
r := new(contentRange)
if parts[0] == "*" {
r.Start = 0
r.End = -1
} else {
offsets := strings.Split(parts[0], "-")
if len(offsets) != 2 {
return nil, errInvalidRange
}
if offset, err := strconv.ParseInt(offsets[0], 10, 64); err == nil {
r.Start = offset
} else {
return nil, errInvalidRange
}
if offset, err := strconv.ParseInt(offsets[1], 10, 64); err == nil {
r.End = offset
} else {
return nil, errInvalidRange
}
// A byte-content-range-spec with a byte-range-resp-spec whose last-
// byte-pos value is less than its first-byte-pos value, or whose
// instance-length value is less than or equal to its last-byte-pos value,
// is invalid. The recipient of an invalid byte-content-range- spec MUST
// ignore it and any content transferred along with it.
if r.End <= r.Start {
return nil, errInvalidRange
}
}
if parts[1] == "*" {
r.Size = -1
return r, nil
} else if size, err := strconv.ParseInt(parts[1], 10, 64); err == nil {
r.Size = size
return r, nil
}
return nil, errInvalidRange
}

View File

@ -1,54 +0,0 @@
package main
import (
"regexp"
"testing"
)
var ContentRangeTests = []struct {
s string
want contentRange
err string
}{
{s: "bytes 0-5/100", want: contentRange{Start: 0, End: 5, Size: 100}},
{s: "bytes 5-20/30", want: contentRange{Start: 5, End: 20, Size: 30}},
{s: "bytes */100", want: contentRange{Start: 0, End: -1, Size: 100}},
{s: "bytes 5-20/*", want: contentRange{Start: 5, End: 20, Size: -1}},
{s: "bytes */*", want: contentRange{Start: 0, End: -1, Size: -1}},
{s: "bytes 0-2147483647/2147483648", want: contentRange{Start: 0, End: 2147483647, Size: 2147483648}},
{s: "bytes 5-20", err: "invalid"},
{s: "bytes 5-5/100", err: "invalid"},
{s: "bytes 5-4/100", err: "invalid"},
{s: "bytes ", err: "invalid"},
{s: "", err: "invalid"},
}
func TestParseContentRange(t *testing.T) {
for _, test := range ContentRangeTests {
t.Logf("testing: %s", test.s)
r, err := parseContentRange(test.s)
if test.err != "" {
if err == nil {
t.Errorf("got no error, but expected: %s", test.err)
continue
}
errMatch := regexp.MustCompile(test.err)
if !errMatch.MatchString(err.Error()) {
t.Errorf("unexpected error: %s, wanted: %s", err, test.err)
continue
}
continue
} else if err != nil {
t.Errorf("unexpected error: %s, wanted: %+v", err, test.want)
continue
}
if *r != test.want {
t.Errorf("got: %+v, wanted: %+v", r, test.want)
continue
}
}
}

View File

@ -1,257 +0,0 @@
package main
import (
"errors"
"fmt"
"io"
"log"
"net/http"
"os"
"path"
"regexp"
"strconv"
"time"
)
// fileRoute matches /files/<id>. Go seems to use \r to terminate header
// values, so to ease bash scripting, the route ignores a trailing \r in the
// route. Better ideas are welcome.
var fileRoute = regexp.MustCompile("^/files/([^/\r\n]+)\r?$")
var filesRoute = regexp.MustCompile("^/files/?$")
var dataStore *DataStore
func init() {
wd, err := os.Getwd()
if err != nil {
panic(err)
}
dataDir := path.Join(wd, "tus_data")
if configDir := os.Getenv("TUSD_DATA_DIR"); configDir != "" {
dataDir = configDir
}
// dataStoreSize limits the storage used by the data store. If exceeded, the
// data store will start garbage collection old files until enough storage is
// available again.
var dataStoreSize int64
dataStoreSize = 1024 * 1024 * 1024
if configStoreSize := os.Getenv("TUSD_DATA_STORE_MAXSIZE"); configStoreSize != "" {
parsed, err := strconv.ParseInt(configStoreSize, 10, 64)
if err != nil {
panic(errors.New("Invalid data store max size configured"))
}
dataStoreSize = parsed
}
log.Print("Datastore directory: ", dataDir)
log.Print("Datastore max size: ", dataStoreSize)
if err := os.MkdirAll(dataDir, 0777); err != nil {
panic(err)
}
dataStore = NewDataStore(dataDir, dataStoreSize)
}
func serveHttp() error {
http.HandleFunc("/", route)
addr := ":1080"
if port := os.Getenv("TUSD_PORT"); port != "" {
addr = ":" + port
}
log.Printf("serving clients at %s", addr)
return http.ListenAndServe(addr, nil)
}
func route(w http.ResponseWriter, r *http.Request) {
start := time.Now()
log.Printf("request: %s %s", r.Method, r.URL.RequestURI())
w.Header().Set("Server", "tusd")
// Allow CORS for almost everything. This needs to be revisted / limited to
// routes and methods that need it.
w.Header().Add("Access-Control-Allow-Origin", "*")
w.Header().Add("Access-Control-Allow-Methods", "HEAD,GET,PUT,POST,DELETE")
w.Header().Add("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Content-Range, Content-Disposition")
w.Header().Add("Access-Control-Expose-Headers", "Location, Range, Content-Disposition")
if r.Method == "OPTIONS" {
reply(w, http.StatusOK, "")
return
}
if r.Method == "POST" && filesRoute.Match([]byte(r.URL.Path)) {
postFiles(w, r)
} else if match := fileRoute.FindStringSubmatch(r.URL.Path); match != nil {
id := match[1]
switch r.Method {
case "HEAD":
headFile(w, r, id)
case "GET":
getFile(w, r, id)
case "PUT":
putFile(w, r, id)
default:
reply(w, http.StatusMethodNotAllowed, "Invalid http method")
}
} else {
reply(w, http.StatusNotFound, "No matching route")
}
duration := time.Since(start)
log.Printf("finished: %s %s (took %s)", r.Method, r.URL.RequestURI(), duration)
}
func reply(w http.ResponseWriter, code int, message string) {
w.WriteHeader(code)
fmt.Fprintf(w, "%d - %s: %s\n", code, http.StatusText(code), message)
}
func postFiles(w http.ResponseWriter, r *http.Request) {
contentRange, err := parseContentRange(r.Header.Get("Content-Range"))
if err != nil {
reply(w, http.StatusBadRequest, err.Error())
return
}
if contentRange.Size == -1 {
reply(w, http.StatusBadRequest, "Content-Range must indicate total file size.")
return
}
contentType := r.Header.Get("Content-Type")
if contentType == "" {
contentType = "application/octet-stream"
}
contentDisposition := r.Header.Get("Content-Disposition")
id := uid()
if err := dataStore.CreateFile(id, contentRange.Size, contentType, contentDisposition); err != nil {
reply(w, http.StatusInternalServerError, err.Error())
return
}
if contentRange.End != -1 {
err := dataStore.WriteFileChunk(id, contentRange.Start, contentRange.End, r.Body)
if os.IsNotExist(err) {
reply(w, http.StatusNotFound, err.Error())
return
} else if err != nil {
reply(w, http.StatusInternalServerError, err.Error())
return
}
}
w.Header().Set("Location", "http://"+r.Host+"/files/"+id)
setFileHeaders(w, id)
w.WriteHeader(http.StatusCreated)
}
func headFile(w http.ResponseWriter, r *http.Request, fileId string) {
// Work around a bug in Go that would cause HEAD responses to hang. Should be
// fixed in future release, see:
// http://code.google.com/p/go/issues/detail?id=4126
w.Header().Set("Content-Length", "0")
setFileHeaders(w, fileId)
}
func getFile(w http.ResponseWriter, r *http.Request, fileId string) {
meta, err := dataStore.GetFileMeta(fileId)
if os.IsNotExist(err) {
reply(w, http.StatusNotFound, err.Error())
return
} else if err != nil {
reply(w, http.StatusInternalServerError, err.Error())
return
}
data, err := dataStore.ReadFile(fileId)
if os.IsNotExist(err) {
reply(w, http.StatusNotFound, err.Error())
return
} else if err != nil {
reply(w, http.StatusInternalServerError, err.Error())
return
}
defer data.Close()
setFileHeaders(w, fileId)
w.Header().Set("Content-Length", strconv.FormatInt(meta.Size, 10))
if _, err := io.CopyN(w, data, meta.Size); err != nil {
log.Printf("getFile: CopyN of fileId %s failed with: %s. Is the upload complete yet?", fileId, err.Error())
return
}
}
func putFile(w http.ResponseWriter, r *http.Request, fileId string) {
var start int64 = 0
var end int64 = 0
contentRange, err := parseContentRange(r.Header.Get("Content-Range"))
if err != nil {
contentLength := r.Header.Get("Content-Length")
end, err = strconv.ParseInt(contentLength, 10, 64)
if err != nil {
reply(w, http.StatusBadRequest, "Invalid content length provided")
}
// we are zero-indexed
end = end - 1
// @TODO: Make sure contentLength matches the content length of the initial
// POST request
} else {
// @TODO: Make sure contentRange.Size matches file size
start = contentRange.Start
end = contentRange.End
}
// @TODO: Check that file exists
err = dataStore.WriteFileChunk(fileId, start, end, r.Body)
if os.IsNotExist(err) {
reply(w, http.StatusNotFound, err.Error())
return
} else if err != nil {
reply(w, http.StatusInternalServerError, err.Error())
return
}
setFileHeaders(w, fileId)
}
func setFileHeaders(w http.ResponseWriter, fileId string) {
meta, err := dataStore.GetFileMeta(fileId)
if os.IsNotExist(err) {
reply(w, http.StatusNotFound, err.Error())
return
} else if err != nil {
reply(w, http.StatusInternalServerError, err.Error())
return
}
rangeHeader := ""
for i, chunk := range meta.Chunks {
rangeHeader += fmt.Sprintf("%d-%d", chunk.Start, chunk.End)
if i+1 < len(meta.Chunks) {
rangeHeader += ","
}
}
if rangeHeader != "" {
w.Header().Set("Range", "bytes="+rangeHeader)
}
w.Header().Set("Content-Type", meta.ContentType)
w.Header().Set("Content-Disposition", meta.ContentDisposition)
}