@@ -13,7 +13,6 @@ import (
13
13
"net/http/cookiejar"
14
14
"net/url"
15
15
"os"
16
- "path/filepath"
17
16
"reflect"
18
17
"strconv"
19
18
"strings"
@@ -23,6 +22,7 @@ import (
23
22
const (
24
23
fileNameKey = "file"
25
24
defaultTimeout = 10 * time.Second
25
+ defaultChunkSize = 5 * 1024 * 1024
26
26
)
27
27
28
28
// {{ spec .title | caseUcfirst }}Error represents an error of a client request
@@ -58,6 +58,7 @@ type Client struct {
58
58
endpoint string
59
59
timeout time.Duration
60
60
selfSigned bool
61
+ chunkSize int64
61
62
}
62
63
63
64
// NewClient initializes a new {{ spec .title | caseUcfirst }} client with a given timeout
@@ -78,6 +79,7 @@ func NewClient() Client {
78
79
client: httpClient,
79
80
timeout: defaultTimeout,
80
81
headers: headers,
82
+ chunkSize: defaultChunkSize,
81
83
}
82
84
}
83
85
@@ -87,8 +89,8 @@ func (clt *Client) String() string {
87
89
88
90
func getDefaultClient(timeout time.Duration) (*http.Client, error) {
89
91
jar, err := cookiejar.New(nil)
90
- if err != nil {
91
- return nil, err
92
+ if err != nil {
93
+ return nil, err
92
94
}
93
95
return & http.Client {
94
96
Jar: jar,
@@ -114,6 +116,11 @@ func (clt *Client) SetSelfSigned(status bool) {
114
116
clt.selfSigned = status
115
117
}
116
118
119
+ // SetChunkSize sets the chunk size for file upload
120
+ func (clt *Client) SetChunkSize(size int64) {
121
+ clt.chunkSize = size
122
+ }
123
+
117
124
// AddHeader add a new custom header that the Client should send on each request
118
125
func (clt *Client) AddHeader(key string, value string) {
119
126
clt.headers[key] = value
@@ -136,59 +143,69 @@ func isFileUpload(headers map[string]interface{}) bool {
136
143
return false
137
144
}
138
145
139
- func (clt *Client) newfileUploadRequest(uri string, params map[string]interface{}, paramName string) (*http.Request , error) {
140
- path , ok := params[paramName].(string )
146
+ func (clt *Client) FileUpload(url string, headers map[string]interface{}, params map[string]interface{}, paramName string, uploadId string ) (*ClientResponse , error) {
147
+ inputFile , ok := params[paramName].(InputFile )
141
148
if !ok {
142
- return nil, os.ErrNotExist
143
- }
144
- if _, err := os.Stat(path); err != nil {
145
- return nil, err
149
+ msg := fmt.Sprintf("invalid input file. params[%s] must be of type InputFile", paramName)
150
+ return nil, errors.New(msg)
146
151
}
147
- file, err := os.Open(path)
152
+
153
+ file, err := os.Open(inputFile.Path)
148
154
if err != nil {
149
155
return nil, err
150
156
}
151
157
defer file.Close()
152
158
153
- body := & bytes.Buffer{}
154
- writer := multipart.NewWriter(body)
155
- part, err := writer.CreateFormFile(paramName, filepath.Base(path))
156
- if err != nil {
157
- return nil, err
158
- }
159
- _, err = io.Copy(part, file)
159
+ fileInfo, err := file.Stat()
160
160
if err != nil {
161
161
return nil, err
162
162
}
163
163
164
- for key, val := range params {
165
- rt := reflect.TypeOf(val)
166
- switch rt.Kind() {
167
- case reflect.Array:
168
- case reflect.Slice:
169
- arr := reflect.ValueOf(val)
170
- for i := 0; i < arr.Len(); i++ {
171
- err = writer.WriteField(fmt.Sprintf("%s[]", key), toString(arr.Index(i)))
172
- if err != nil {
173
- return nil, err
174
- }
175
- }
176
- default:
177
- err = writer.WriteField(key, toString(val))
178
- if err != nil {
179
- return nil, err
180
- }
164
+ inputFile.Data = make([]byte, clt.chunkSize)
165
+
166
+ var result *ClientResponse
167
+
168
+ numChunks := fileInfo.Size() / clt.chunkSize
169
+ if fileInfo.Size()%clt.chunkSize != 0 {
170
+ numChunks++
171
+ }
172
+ var currentChunk int64 = 0
173
+ if uploadId != "" && uploadId != "unique()" {
174
+ resp, err := clt.Call("GET", url + "/" + uploadId, nil, nil)
175
+ if err == nil {
176
+ currentChunk = int64(resp.Result.(map[string]interface{})["chunksUploaded"].(float64))
181
177
}
182
178
}
179
+ for i := currentChunk; i < numChunks; i++ {
180
+ chunkSize := clt.chunkSize
181
+ offset := int64(i) * chunkSize
182
+ if i == numChunks-1 {
183
+ chunkSize = fileInfo.Size() - offset
184
+ inputFile.Data = make([]byte, chunkSize)
185
+ }
186
+ _, err := file.ReadAt(inputFile.Data, offset)
187
+ if err != nil && err != io.EOF {
188
+ return nil, err
189
+ }
190
+ params[paramName] = inputFile
191
+ if uploadId != "" {
192
+ headers["x-appwrite-id"] = uploadId
193
+ }
194
+ totalSize := fileInfo.Size()
195
+ start := offset
196
+ end := offset + clt.chunkSize - 1
197
+ if end > totalSize {
198
+ end = totalSize
199
+ }
200
+ headers["content-range"] = fmt.Sprintf("bytes %d-%d/%d", start, end, totalSize)
201
+ result, err = clt.Call("POST", url, headers, params)
202
+ if err != nil {
203
+ return nil, err
204
+ }
183
205
184
- err = writer.Close()
185
- if err != nil {
186
- return nil, err
206
+ uploadId, _ = result.Result.(map[string]interface{})["$id"].(string)
187
207
}
188
-
189
- req, err := http.NewRequest("POST", uri, body)
190
- clt.AddHeader("content-type", writer.FormDataContentType())
191
- return req, err
208
+ return result, nil
192
209
}
193
210
194
211
// Call an API using Client
@@ -218,10 +235,53 @@ func (clt *Client) Call(method string, path string, headers map[string]interface
218
235
if !isPost {
219
236
return nil, errors.New("fileupload needs POST Request")
220
237
}
221
- req, err = clt.newfileUploadRequest(urlPath, params, fileNameKey)
238
+ var body bytes.Buffer
239
+ writer := multipart.NewWriter(& body)
240
+ if val, ok := params[fileNameKey]; ok {
241
+ if file, ok := val.(InputFile); ok {
242
+ fileName := file.Name
243
+ fileData := file.Data
244
+ fw, err := writer.CreateFormFile(fileNameKey, fileName)
245
+ if err != nil {
246
+ return nil, err
247
+ }
248
+ _, err = io.Copy(fw, bytes.NewReader(fileData))
249
+ if err != nil {
250
+ return nil, err
251
+ }
252
+ delete(params, fileNameKey)
253
+ } else {
254
+ return nil, errors.New("invalid input file")
255
+ }
256
+ }
257
+ for key, val := range params {
258
+ rt := reflect.TypeOf(val)
259
+ switch rt.Kind() {
260
+ case reflect.Array:
261
+ case reflect.Slice:
262
+ arr := reflect.ValueOf(val)
263
+ for i := 0; i < arr.Len(); i++ {
264
+ err = writer.WriteField(fmt.Sprintf("%s[]", key), toString(arr.Index(i)))
265
+ if err != nil {
266
+ return nil, err
267
+ }
268
+ }
269
+ default:
270
+ err = writer.WriteField(key, toString(val))
271
+ if err != nil {
272
+ return nil, err
273
+ }
274
+ }
275
+ }
276
+ err = writer.Close()
277
+ if err != nil {
278
+ return nil, err
279
+ }
280
+ req, err = http.NewRequest(method, urlPath, & body)
222
281
if err != nil {
223
282
return nil, err
224
283
}
284
+ headers["content-type"] = writer.FormDataContentType()
225
285
} else {
226
286
if !isGet {
227
287
var reqBody *strings.Reader
@@ -385,4 +445,4 @@ func toString(arg interface{}) string {
385
445
default:
386
446
return fmt.Sprintf("%s", v)
387
447
}
388
- }
448
+ }
0 commit comments