Skip to content

Commit c248c61

Browse files
committed
Add code
1 parent bb86e53 commit c248c61

8 files changed

+422
-0
lines changed

.golangci.yml

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# This file contains all available configuration options
2+
# with their default values.
3+
# Config file reference: https://golangci-lint.run/usage/configuration/#config-file
4+
# Linters list: https://golangci-lint.run/usage/linters
5+
6+
run:
7+
timeout: 5m
8+
modules-download-mode: readonly
9+
skip-dirs:
10+
- charts
11+
- config
12+
13+
linters:
14+
enable:
15+
- gofumpt
16+
- revive
17+
- gocritic
18+
- govet
19+
- staticcheck
20+
- errname
21+
linters-settings:
22+
govet:
23+
disable:
24+
- composites
25+
26+
issues:
27+
max-issues-per-linter: 0
28+
max-same-issues: 0

.pre-commit-config.yaml

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
repos:
2+
- repo: https://github.com/golangci/golangci-lint
3+
rev: v1.50.1
4+
hooks:
5+
- id: golangci-lint

capabilities.go

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package tusgo
2+
3+
type ServerCapabilities struct {
4+
Extensions []string
5+
MaxSize int64
6+
ProtocolVersions []string
7+
}

client.go

+172
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
package tusgo
2+
3+
import (
4+
"context"
5+
"encoding/base64"
6+
"fmt"
7+
"net/http"
8+
"net/url"
9+
"strconv"
10+
"strings"
11+
)
12+
13+
func NewClient(client *http.Client, baseURL *url.URL) *Client {
14+
c := &Client{BaseURL: baseURL}
15+
if client == nil {
16+
c.client = http.DefaultClient
17+
}
18+
return c
19+
}
20+
21+
type Client struct {
22+
BaseURL *url.URL
23+
ProtocolVersion string
24+
client *http.Client
25+
capabilities *ServerCapabilities
26+
ctx context.Context
27+
}
28+
29+
func (c *Client) WithContext(ctx context.Context) *Client {
30+
c.ctx = ctx
31+
return c
32+
}
33+
34+
func (c *Client) CreateFile(f *File) (response *http.Response, err error) {
35+
if err = c.ensureExtension("creation"); err != nil {
36+
return
37+
}
38+
39+
var req *http.Request
40+
var loc *url.URL
41+
if loc, err = url.Parse(f.Location); err != nil {
42+
return
43+
}
44+
u := c.BaseURL.ResolveReference(loc).String()
45+
if req, err = http.NewRequest(http.MethodPost, u, nil); err != nil {
46+
return
47+
}
48+
49+
req.Header.Set("Content-Length", strconv.FormatInt(0, 10))
50+
req.Header.Set("Upload-Length", strconv.FormatInt(f.RemoteSize, 10))
51+
var meta string
52+
if meta, err = EncodeMetadata(f.Metadata); err != nil {
53+
return
54+
}
55+
req.Header.Set("Upload-Metadata", meta)
56+
57+
if response, err = c.tusRequest(req); err != nil {
58+
return
59+
}
60+
defer response.Body.Close()
61+
if response.StatusCode == http.StatusCreated {
62+
f.Location = response.Header.Get("Location")
63+
}
64+
65+
return
66+
}
67+
68+
func (c *Client) DeleteFile(f *File) (response *http.Response, err error) {
69+
if err = c.ensureExtension("termination"); err != nil {
70+
return
71+
}
72+
73+
var req *http.Request
74+
var loc *url.URL
75+
if loc, err = url.Parse(f.Location); err != nil {
76+
return
77+
}
78+
u := c.BaseURL.ResolveReference(loc).String()
79+
if req, err = http.NewRequest(http.MethodDelete, u, nil); err != nil {
80+
return
81+
}
82+
if response, err = c.tusRequest(req); err != nil {
83+
return
84+
}
85+
defer response.Body.Close()
86+
return
87+
}
88+
89+
func (c *Client) GetCapabilities() (caps *ServerCapabilities, response *http.Response, err error) {
90+
var req *http.Request
91+
if req, err = http.NewRequest(http.MethodOptions, c.BaseURL.String(), nil); err != nil {
92+
return
93+
}
94+
if response, err = c.tusRequest(req); err != nil {
95+
return
96+
}
97+
defer response.Body.Close()
98+
if response.StatusCode != http.StatusNoContent && response.StatusCode != http.StatusOK {
99+
return
100+
}
101+
102+
caps = &ServerCapabilities{}
103+
if v := response.Header.Get("Tus-Max-Size"); v != "" {
104+
if caps.MaxSize, err = strconv.ParseInt(v, 10, 64); err != nil {
105+
err = fmt.Errorf("cannot parse Tus-Max-Size integer value: %w", err)
106+
return
107+
}
108+
}
109+
if v := response.Header.Get("Tus-Extension"); v != "" {
110+
caps.Extensions = strings.Split(v, ",")
111+
}
112+
if v := response.Header.Get("Tus-Version"); v != "" {
113+
caps.ProtocolVersions = strings.Split(v, ",")
114+
}
115+
116+
return
117+
}
118+
119+
func (c *Client) tusRequest(req *http.Request) (response *http.Response, err error) {
120+
if req.Method != http.MethodOptions {
121+
req.Header.Set("Tus-Resumable", c.ProtocolVersion)
122+
}
123+
if c.ctx != nil {
124+
req = req.WithContext(c.ctx)
125+
}
126+
response, err = c.client.Do(req)
127+
if response.StatusCode == http.StatusPreconditionFailed {
128+
versions := response.Header.Get("Tus-Version")
129+
err = fmt.Errorf("server does not support version %s, supported versions: %s", c.ProtocolVersion, versions)
130+
} else if v := response.Header.Get("Tus-Resumable"); v != c.ProtocolVersion {
131+
err = fmt.Errorf("server unexpectedly responded Tus protocol version %s, but we requested version %s", v, c.ProtocolVersion)
132+
}
133+
return
134+
}
135+
136+
func (c *Client) ensureExtension(extension string) error {
137+
if err := c.maybeUpdateCapabilities(); err != nil {
138+
return fmt.Errorf("cannot obtain server capabilities: %w", err)
139+
}
140+
for _, e := range c.capabilities.Extensions {
141+
if extension == e {
142+
return nil
143+
}
144+
}
145+
return fmt.Errorf("server does not support %q extension", extension)
146+
}
147+
148+
func (c *Client) maybeUpdateCapabilities() (err error) {
149+
var response *http.Response
150+
if c.capabilities == nil {
151+
if c.capabilities, response, err = c.GetCapabilities(); err != nil {
152+
return
153+
}
154+
if response.StatusCode != http.StatusNoContent && response.StatusCode != http.StatusOK {
155+
return fmt.Errorf("server returned HTTP code %d", response.StatusCode)
156+
}
157+
}
158+
return
159+
}
160+
161+
func EncodeMetadata(metadata map[string]string) (string, error) {
162+
var encoded []string
163+
164+
for k, v := range metadata {
165+
if strings.Contains(k, " ") {
166+
return "", fmt.Errorf("key %q contains spaces", k)
167+
}
168+
encoded = append(encoded, fmt.Sprintf("%s %s", k, base64.StdEncoding.EncodeToString([]byte(v))))
169+
}
170+
171+
return strings.Join(encoded, ","), nil
172+
}

error.go

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
package tusgo

file.go

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package tusgo
2+
3+
type File struct {
4+
Metadata map[string]string
5+
RemoteSize int64 // TODO: may be deferred
6+
Location string
7+
}

go.mod

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module github.com/bdragon300/tusgo
2+
3+
go 1.17

0 commit comments

Comments
 (0)