This repository has been archived by the owner on Apr 16, 2021. It is now read-only.
/
upload.go
195 lines (165 loc) · 5.27 KB
/
upload.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
package skynet
import (
"bytes"
"encoding/json"
"fmt"
"io"
"mime/multipart"
"net/url"
"os"
gopath "path"
"path/filepath"
"strings"
"gitlab.com/NebulousLabs/errors"
)
type (
// UploadData contains data to upload, indexed by filenames.
UploadData map[string]io.Reader
// UploadOptions contains the options used for uploads.
UploadOptions struct {
Options
// PortalFileFieldName is the fieldName for files on the portal.
PortalFileFieldName string
// PortalDirectoryFileFieldName is the fieldName for directory files on
// the portal.
PortalDirectoryFileFieldName string
// CustomFilename is the custom filename to use for the upload. If this
// is empty, the filename of the file being uploaded will be used by
// default.
CustomFilename string
// CustomDirname is the custom name of the directory. If this is empty,
// the base name of the directory being uploaded will be used by
// default.
CustomDirname string
// SkykeyName is the name of the skykey used to encrypt the upload.
SkykeyName string
// SkykeyID is the ID of the skykey used to encrypt the upload.
SkykeyID string
}
// UploadResponse contains the response for uploads.
UploadResponse struct {
// Skylink is the returned skylink.
Skylink string `json:"skylink"`
}
)
var (
// DefaultUploadOptions contains the default upload options.
DefaultUploadOptions = UploadOptions{
Options: DefaultOptions("/skynet/skyfile"),
PortalFileFieldName: "file",
PortalDirectoryFileFieldName: "files[]",
CustomFilename: "",
CustomDirname: "",
SkykeyName: "",
SkykeyID: "",
}
)
// Upload uploads the given generic data and returns the skylink.
func Upload(uploadData UploadData, opts UploadOptions) (skylink string, err error) {
// prepare formdata
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
var fieldname string
var filename string
if len(uploadData) == 1 {
fieldname = opts.PortalFileFieldName
} else {
if opts.CustomDirname == "" {
return "", errors.New("CustomDirname must be set when uploading multiple files")
}
fieldname = opts.PortalDirectoryFileFieldName
filename = opts.CustomDirname
}
values := url.Values{}
values.Set("filename", filename)
values.Set("skykeyname", opts.SkykeyName)
values.Set("skykeyid", opts.SkykeyID)
url := makeURL(opts.PortalURL, opts.EndpointPath, values)
for filename, data := range uploadData {
part, err := writer.CreateFormFile(fieldname, filename)
if err != nil {
return "", errors.AddContext(err, fmt.Sprintf("could not create form file for file %v", filename))
}
_, err = io.Copy(part, data)
if err != nil {
return "", errors.AddContext(err, fmt.Sprintf("could not copy data for file %v", filename))
}
}
err = writer.Close()
if err != nil {
return "", errors.AddContext(err, "could not close writer")
}
opts.customContentType = writer.FormDataContentType()
resp, err := executeRequest("POST", url, body, opts.Options)
if err != nil {
return "", errors.AddContext(err, "could not execute request")
}
respBody, err := parseResponseBody(resp)
if err != nil {
return "", errors.AddContext(err, "could not parse response body")
}
var apiResponse UploadResponse
err = json.Unmarshal(respBody.Bytes(), &apiResponse)
if err != nil {
return "", errors.AddContext(err, "could not unmarshal response JSON")
}
return fmt.Sprintf("%s%s", URISkynetPrefix, apiResponse.Skylink), nil
}
// UploadFile uploads a file to Skynet and returns the skylink.
func UploadFile(path string, opts UploadOptions) (skylink string, err error) {
path = gopath.Clean(path)
// Open the file.
file, err := os.Open(gopath.Clean(path)) // Clean again to prevent lint error.
if err != nil {
return "", errors.AddContext(err, fmt.Sprintf("could not open file %v", path))
}
defer func() {
err = errors.Extend(err, file.Close())
}()
// Set filename.
filename := filepath.Base(path)
if opts.CustomFilename != "" {
filename = opts.CustomFilename
}
uploadData := make(UploadData)
uploadData[filename] = file
return Upload(uploadData, opts)
}
// UploadDirectory uploads a local directory to Skynet and returns the skylink.
func UploadDirectory(path string, opts UploadOptions) (skylink string, err error) {
path = gopath.Clean(path)
// Verify the given path is a directory.
info, err := os.Stat(path)
if err != nil {
return "", errors.AddContext(err, "error retrieving path info")
}
if !info.IsDir() {
return "", fmt.Errorf("given path %v is not a directory", path)
}
// Find all files in the given directory.
files, err := walkDirectory(path)
if err != nil {
return "", errors.AddContext(err, "error walking directory")
}
// Set DirName.
if opts.CustomDirname == "" {
opts.CustomDirname = filepath.Base(path)
}
// prepare formdata
uploadData := make(UploadData)
for _, filepath := range files {
file, err := os.Open(gopath.Clean(filepath)) // Clean again to prevent lint error.
if err != nil {
return "", errors.AddContext(err, "error opening file")
}
// Remove the base path before uploading. Any ending '/' was removed
// from `path` with `Clean`.
basepath := path
if basepath != "/" {
basepath += "/"
}
filepath = strings.TrimPrefix(filepath, basepath)
uploadData[filepath] = file
}
return Upload(uploadData, opts)
}