Skip to content

Commit 09e739c

Browse files
feat: [OCISDEV-906] implement the Kiteworks write path, versions and locking (#739)
1 parent db0569f commit 09e739c

10 files changed

Lines changed: 1842 additions & 125 deletions

File tree

‎pkg/storage/fs/kiteworks/kiteworks.go‎

Lines changed: 406 additions & 58 deletions
Large diffs are not rendered by default.

‎pkg/storage/fs/kiteworks/kiteworks_test.go‎

Lines changed: 474 additions & 21 deletions
Large diffs are not rendered by default.

‎pkg/storage/fs/kiteworks/kwlib/client.go‎

Lines changed: 178 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package kwlib
22

33
import (
44
"bytes"
5+
"context"
56
"crypto/tls"
67
"encoding/json"
78
"errors"
@@ -18,6 +19,8 @@ import (
1819
"github.com/rs/zerolog"
1920
)
2021

22+
const uploadChunkSize = 8 << 20 // 8 MiB
23+
2124
func NewClientFactory(server, agentString string, insecure bool) *APIClientFactory {
2225
transport := &http.Transport{
2326
Proxy: http.ProxyFromEnvironment,
@@ -36,28 +39,35 @@ func NewClientFactory(server, agentString string, insecure bool) *APIClientFacto
3639
// #nosec
3740
transport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
3841
}
42+
43+
uploadTransport := transport.Clone()
44+
uploadTransport.ResponseHeaderTimeout = 30 * time.Second
45+
3946
return &APIClientFactory{
40-
server: server,
41-
agentString: agentString,
42-
httpClient: &http.Client{Transport: transport, Timeout: 15 * time.Second},
47+
server: server,
48+
agentString: agentString,
49+
httpClient: &http.Client{Transport: transport, Timeout: 15 * time.Second},
50+
uploadClient: &http.Client{Transport: uploadTransport},
4351
}
4452
}
4553

4654
type APIClientFactory struct {
47-
server string
48-
agentString string
49-
httpClient *http.Client
55+
server string
56+
agentString string
57+
httpClient *http.Client
58+
uploadClient *http.Client
5059
}
5160

5261
type APIClient struct {
53-
server string
54-
agentString string
55-
logger *zerolog.Logger
56-
host string
57-
token string
58-
requestId string
59-
remoteAddr string
60-
httpClient *http.Client
62+
server string
63+
agentString string
64+
logger *zerolog.Logger
65+
host string
66+
token string
67+
requestId string
68+
remoteAddr string
69+
httpClient *http.Client
70+
uploadClient *http.Client
6171
}
6272

6373
func decodeJSON(body io.ReadCloser, out any) error {
@@ -67,13 +77,14 @@ func decodeJSON(body io.ReadCloser, out any) error {
6777

6878
func (f *APIClientFactory) Build(host, requestId, remoteAddr, token string, l *zerolog.Logger) *APIClient {
6979
return &APIClient{
70-
token: token,
71-
server: f.server,
72-
host: host,
73-
logger: l,
74-
requestId: requestId,
75-
remoteAddr: remoteAddr,
76-
httpClient: f.httpClient,
80+
token: token,
81+
server: f.server,
82+
host: host,
83+
logger: l,
84+
requestId: requestId,
85+
remoteAddr: remoteAddr,
86+
httpClient: f.httpClient,
87+
uploadClient: f.uploadClient,
7788
}
7889
}
7990

@@ -94,7 +105,7 @@ func (c *APIClient) GetTopFolders() (*DirectoryInfo, error) {
94105
}
95106

96107
func (c *APIClient) GetFolderByID(id string) (*FileInfo, error) {
97-
request, err := c.NewGetRequest(fmt.Sprintf("/rest/folders/%s", id))
108+
request, err := c.NewGetRequest(fmt.Sprintf("/rest/folders/%s?with=(permissions)", id))
98109
if err != nil {
99110
return nil, err
100111
}
@@ -148,7 +159,7 @@ func (c *APIClient) Search(path string) (*FileInfo, error) {
148159
}
149160

150161
func (c *APIClient) GetFileByID(id string) (*FileInfo, error) {
151-
request, err := c.NewGetRequest(fmt.Sprintf("/rest/files/%s", id))
162+
request, err := c.NewGetRequest(fmt.Sprintf("/rest/files/%s?with=(permissions,lockUser)", id))
152163
if err != nil {
153164
return nil, err
154165
}
@@ -194,16 +205,16 @@ func (c *APIClient) GetUser(id string) (*User, error) {
194205
return out, nil
195206
}
196207

197-
func (c *APIClient) GetQuotaInfo() (*QuotaInfo, error) {
198-
request, err := c.NewGetRequest("/rest/quotas")
208+
func (c *APIClient) GetFolderQuota(folderID string) (*FolderQuota, error) {
209+
request, err := c.NewGetRequest(fmt.Sprintf("/rest/folders/%s/quota", folderID))
199210
if err != nil {
200211
return nil, err
201212
}
202213
response, err := c.SendRequest(request)
203214
if err != nil {
204215
return nil, err
205216
}
206-
out := &QuotaInfo{}
217+
out := &FolderQuota{}
207218
if err := decodeJSON(response.Body, out); err != nil {
208219
return nil, err
209220
}
@@ -251,12 +262,20 @@ func (c *APIClient) CreateFolder(id string, payload CreateDirRequest) (string, e
251262
}
252263

253264
func (c *APIClient) InitializeUpload(parentID, name string, size int64, numberOfChunks int) (*UploadResult, error) {
265+
return c.initializeUpload(fmt.Sprintf("/rest/folders/%s/actions/initiateUpload", parentID), name, size, numberOfChunks)
266+
}
267+
268+
func (c *APIClient) InitializeVersionUpload(fileID, name string, size int64, numberOfChunks int) (*UploadResult, error) {
269+
return c.initializeUpload(fmt.Sprintf("/rest/files/%s/actions/initiateUpload", fileID), name, size, numberOfChunks)
270+
}
271+
272+
func (c *APIClient) initializeUpload(path, name string, size int64, numberOfChunks int) (*UploadResult, error) {
254273
payload := InitializeUpload{
255274
FileName: name,
256275
TotalSize: size,
257276
TotalChunks: numberOfChunks,
258277
}
259-
request, err := c.NewPostRequest(fmt.Sprintf("/rest/folders/%s/actions/initiateUpload", parentID), payload)
278+
request, err := c.NewPostRequest(path, payload)
260279
if err != nil {
261280
return nil, err
262281
}
@@ -271,7 +290,20 @@ func (c *APIClient) InitializeUpload(parentID, name string, size int64, numberOf
271290
return out, nil
272291
}
273292

274-
func (c *APIClient) UploadChunk(uploadURI, name string, file io.Reader, chunkIndex int, chunk int64, isLastChunk bool) (*FileInfo, error) {
293+
func (c *APIClient) TerminateUpload(uploadID int64) error {
294+
request, err := c.newRequest("DELETE", fmt.Sprintf("/rest/uploads/%d", uploadID), nil)
295+
if err != nil {
296+
return err
297+
}
298+
response, err := c.SendRequest(request)
299+
if err != nil {
300+
return err
301+
}
302+
response.Body.Close()
303+
return nil
304+
}
305+
306+
func (c *APIClient) UploadChunk(ctx context.Context, uploadURI, name string, file io.Reader, chunkIndex int, chunk int64, isLastChunk bool) (*FileInfo, error) {
275307
body := new(bytes.Buffer)
276308
writer := multipart.NewWriter(body)
277309
part, err := writer.CreateFormFile("content", name)
@@ -296,13 +328,14 @@ func (c *APIClient) UploadChunk(uploadURI, name string, file io.Reader, chunkInd
296328
if err != nil {
297329
return nil, err
298330
}
331+
request = request.WithContext(ctx)
299332
request.Header.Set("Content-Type", writer.FormDataContentType())
300333
if isLastChunk {
301334
q := request.URL.Query()
302335
q.Add("returnEntity", "true")
303336
request.URL.RawQuery = q.Encode()
304337
}
305-
response, err := c.SendRequest(request)
338+
response, err := c.sendWith(c.uploadClient, request)
306339
if err != nil {
307340
return nil, err
308341
}
@@ -313,10 +346,121 @@ func (c *APIClient) UploadChunk(uploadURI, name string, file io.Reader, chunkInd
313346
}
314347
return out, nil
315348
}
349+
// drained so the connection can be reused across chunks
350+
_, _ = io.Copy(io.Discard, response.Body)
316351
response.Body.Close()
317352
return nil, nil
318353
}
319354

355+
func (c *APIClient) MoveFolder(id, destinationFolderID string) error {
356+
request, err := c.NewPostRequest(
357+
fmt.Sprintf("/rest/folders/%s/actions/move", id),
358+
MoveFolderRequest{DestinationFolderID: destinationFolderID},
359+
)
360+
if err != nil {
361+
return err
362+
}
363+
_, err = c.SendRequest(request)
364+
return err
365+
}
366+
367+
// name must be the file's current name: the server requires it even for a version upload.
368+
func (c *APIClient) UploadFileVersion(ctx context.Context, fileID, name string, body io.Reader, length int64) error {
369+
chunks := chunkCount(length)
370+
session, err := c.InitializeVersionUpload(fileID, name, length, chunks)
371+
if err != nil {
372+
return err
373+
}
374+
for i := 0; i < chunks; i++ {
375+
size := int64(uploadChunkSize)
376+
if remaining := length - int64(i)*uploadChunkSize; remaining < size {
377+
size = remaining
378+
}
379+
if _, err := c.UploadChunk(ctx, session.URI, name, body, i, size, i == chunks-1); err != nil {
380+
if termErr := c.TerminateUpload(session.ID); termErr != nil {
381+
c.logger.Warn().Err(termErr).Int64("uploadID", session.ID).Msg("could not terminate kiteworks upload session")
382+
}
383+
return err
384+
}
385+
}
386+
return nil
387+
}
388+
389+
func chunkCount(length int64) int {
390+
if length <= 0 {
391+
return 1
392+
}
393+
return int((length + uploadChunkSize - 1) / uploadChunkSize)
394+
}
395+
396+
func (c *APIClient) GetFileVersions(fileID string) ([]Version, error) {
397+
req, err := c.NewGetRequest(fmt.Sprintf("/rest/files/%s/versions", fileID))
398+
if err != nil {
399+
return nil, err
400+
}
401+
resp, err := c.SendRequest(req)
402+
if err != nil {
403+
return nil, err
404+
}
405+
out := &VersionList{}
406+
if err := decodeJSON(resp.Body, out); err != nil {
407+
return nil, err
408+
}
409+
return out.Data, nil
410+
}
411+
412+
func (c *APIClient) DeleteFileVersion(fileID, versionID string) error {
413+
req, err := c.newRequest("DELETE", fmt.Sprintf("/rest/files/%s/versions/%s", fileID, versionID), nil)
414+
if err != nil {
415+
return err
416+
}
417+
_, err = c.SendRequest(req)
418+
return err
419+
}
420+
421+
func (c *APIClient) PromoteFileVersion(fileID, versionID string) error {
422+
req, err := c.newRequest("POST", fmt.Sprintf("/rest/files/%s/versions/%s/actions/promote", fileID, versionID), nil)
423+
if err != nil {
424+
return err
425+
}
426+
_, err = c.SendRequest(req)
427+
return err
428+
}
429+
430+
func (c *APIClient) GetVersionContents(fileID, versionID string) (*http.Response, error) {
431+
req, err := c.NewGetRequest(fmt.Sprintf("/rest/files/%s/versions/%s/content", fileID, versionID))
432+
if err != nil {
433+
return nil, err
434+
}
435+
return c.SendRequest(req)
436+
}
437+
438+
func (c *APIClient) LockFile(fileID string) error {
439+
req, err := c.newRequest("PATCH", fmt.Sprintf("/rest/files/%s/actions/lock", fileID), nil)
440+
if err != nil {
441+
return err
442+
}
443+
resp, err := c.SendRequest(req)
444+
if err != nil {
445+
return err
446+
}
447+
resp.Body.Close()
448+
return nil
449+
}
450+
451+
func (c *APIClient) UnlockFile(fileID string) error {
452+
req, err := c.newRequest("PATCH", fmt.Sprintf("/rest/files/%s/actions/unlock", fileID), nil)
453+
if err != nil {
454+
return err
455+
}
456+
resp, err := c.SendRequest(req)
457+
if err != nil {
458+
return err
459+
}
460+
resp.Body.Close()
461+
return nil
462+
}
463+
320464
func (c *APIClient) DeleteFolder(id string) error {
321465
request, err := c.newRequest("DELETE", fmt.Sprintf("/rest/folders/%s", id), nil)
322466
if err != nil {
@@ -432,7 +576,11 @@ func (c *APIClient) newRequest(method, path string, body io.Reader) (*http.Reque
432576
}
433577

434578
func (c *APIClient) SendRequest(req *http.Request) (*http.Response, error) {
435-
response, err := c.httpClient.Do(req)
579+
return c.sendWith(c.httpClient, req)
580+
}
581+
582+
func (c *APIClient) sendWith(client *http.Client, req *http.Request) (*http.Response, error) {
583+
response, err := client.Do(req)
436584
if err != nil {
437585
c.logger.Debug().Str("method", req.Method).Str("path", req.URL.String()).Err(err).Msg("kiteworks API call errored")
438586
return nil, err

0 commit comments

Comments
 (0)