package main import ( "crypto/sha256" "encoding/hex" "fmt" "io" "os" "path/filepath" "github.com/go-resty/resty/v2" "github.com/schollz/progressbar/v3" photosapi "gitlab.celogeek.com/photos/api/internal/photos/api" ) type UploadCommand struct { Url string `short:"u" long:"url" description:"Url of the instance" required:"true"` Token string `short:"t" long:"token" description:"Token of the instance" required:"true"` File string `short:"f" long:"file" description:"File to upload" required:"true"` Workers uint32 `short:"w" long:"workers" description:"Number of workers for uploading chunks" default:"4"` } type UploadCreate struct { UploadId string `json:"upload_id"` } type UploadPartResult struct { UploadId string `json:"upload_id"` Part uint `json:"part"` Size uint `json:"size"` PartSha256 string `json:"sha256"` } func (c *UploadCommand) Cli() *resty.Client { return resty.New().SetBaseURL(c.Url).SetAuthScheme("Private").SetAuthToken(c.Token) } func (c *UploadCommand) Execute(args []string) error { cli := c.Cli() resp, err := cli.R().SetError(&ResponseError{}).SetResult(&UploadCreate{}).Post("/upload") if err != nil { return err } if resp.IsError() { return resp.Error().(*ResponseError) } uploadId := resp.Result().(*UploadCreate).UploadId f, err := os.Open(c.File) if err != nil { return err } defer f.Close() st, err := f.Stat() if err != nil { return err } progress := progressbar.DefaultBytes(st.Size(), fmt.Sprintf("Uploading %s", filepath.Base(c.File))) defer progress.Close() tee := io.TeeReader(f, progress) b := make([]byte, photosapi.MaxUploadPartSize) parts := 0 completesha256 := sha256.New() for { n, err := tee.Read(b) if err != nil { if err == io.EOF { break } else { return err } } parts++ partsha256 := sha256.New() partsha256.Write(b[:n]) completesha256.Write(b[:n]) resp, err := cli. R(). SetError(&ResponseError{}). SetResult(&UploadPartResult{}). SetQueryParam("part", fmt.Sprint(parts)). SetQueryParam("sha256", hex.EncodeToString(partsha256.Sum(nil))). SetBody(b[:n]). SetPathParam("id", uploadId). Put("/upload/{id}") if err != nil { return err } if resp.IsError() { return resp.Error().(*ResponseError) } } fmt.Printf( "Upload: %s\nParts: %d\n", uploadId, parts, ) resp, err = cli. R(). SetError(&ResponseError{}). SetPathParam("id", uploadId). SetBody(&photosapi.UploadCompleteRequest{ Sha256: hex.EncodeToString(completesha256.Sum(nil)), Parts: uint(parts), Name: filepath.Base(c.File), }). Post("/upload/{id}") if err != nil { return err } if resp.IsError() { return resp.Error().(*ResponseError) } fmt.Printf("Response: %s\n", resp.Body()) cli.R().SetPathParam("id", uploadId).Delete("/upload/{id}") return nil } func init() { parser.AddCommand("upload", "Upload a file", "", &UploadCommand{}) }