| @@ -95,6 +95,6 @@ func UpdateFileChunk(fileChunk *FileChunk) error { | |||||
| func updateFileChunk(e Engine, fileChunk *FileChunk) error { | func updateFileChunk(e Engine, fileChunk *FileChunk) error { | ||||
| var sess *xorm.Session | var sess *xorm.Session | ||||
| sess = e.Where("uuid = ?", fileChunk.UUID) | sess = e.Where("uuid = ?", fileChunk.UUID) | ||||
| _, err := sess.Cols("is_uploaded", "completed_parts").Update(fileChunk) | |||||
| _, err := sess.Cols("is_uploaded").Update(fileChunk) | |||||
| return err | return err | ||||
| } | } | ||||
| @@ -16,6 +16,7 @@ package obs | |||||
| import ( | import ( | ||||
| "errors" | "errors" | ||||
| "fmt" | "fmt" | ||||
| "github.com/unknwon/com" | |||||
| "io" | "io" | ||||
| "net/http" | "net/http" | ||||
| "os" | "os" | ||||
| @@ -788,3 +789,54 @@ func (obsClient ObsClient) GetBucketRequestPaymentWithSignedUrl(signedUrl string | |||||
| } | } | ||||
| return | return | ||||
| } | } | ||||
| func (obsClient ObsClient) CreateUploadPartSignedUrl(bucketName, objectKey, uploadId string, partNumber int, partSize int64) (string, error) { | |||||
| requestURL := "" | |||||
| input := &UploadPartInput{} | |||||
| input.Bucket = bucketName | |||||
| input.Key = objectKey | |||||
| input.PartNumber = partNumber | |||||
| input.UploadId = uploadId | |||||
| //input.ContentMD5 = _input.ContentMD5 | |||||
| //input.SourceFile = _input.SourceFile | |||||
| //input.Offset = _input.Offset | |||||
| input.PartSize = partSize | |||||
| //input.SseHeader = _input.SseHeader | |||||
| //input.Body = _input.Body | |||||
| params, headers, _, err := input.trans(obsClient.conf.signature == SignatureObs) | |||||
| if err != nil { | |||||
| return requestURL, err | |||||
| } | |||||
| if params == nil { | |||||
| params = make(map[string]string) | |||||
| } | |||||
| if headers == nil { | |||||
| headers = make(map[string][]string) | |||||
| } | |||||
| var extensions []extensionOptions | |||||
| for _, extension := range extensions { | |||||
| if extensionHeader, ok := extension.(extensionHeaders); ok { | |||||
| _err := extensionHeader(headers, obsClient.conf.signature == SignatureObs) | |||||
| if _err != nil { | |||||
| doLog(LEVEL_WARN, fmt.Sprintf("set header with error: %v", _err)) | |||||
| } | |||||
| } else { | |||||
| doLog(LEVEL_WARN, "Unsupported extensionOptions") | |||||
| } | |||||
| } | |||||
| headers["Content-Length"] = []string{com.ToStr(partNumber,10)} | |||||
| requestURL, err = obsClient.doAuth(HTTP_PUT, bucketName, objectKey, params, headers, "") | |||||
| if err != nil { | |||||
| return requestURL, nil | |||||
| } | |||||
| return requestURL, nil | |||||
| } | |||||
| @@ -100,20 +100,54 @@ func CompleteObsMultiPartUpload(uuid string, uploadID string) error { | |||||
| return nil | return nil | ||||
| } | } | ||||
| func ObsUploadPart(uuid string, uploadId string, partNumber int, partSize int64, partReader io.Reader) error { | |||||
| func ObsUploadPart(uuid string, uploadId string, partNumber int, partSize int64, body io.Reader) (string, error) { | |||||
| input := &obs.UploadPartInput{} | input := &obs.UploadPartInput{} | ||||
| input.PartNumber = partNumber | input.PartNumber = partNumber | ||||
| input.Key = strings.TrimPrefix(path.Join(setting.BasePath, path.Join(uuid[0:1], uuid[1:2], uuid)), "/") | input.Key = strings.TrimPrefix(path.Join(setting.BasePath, path.Join(uuid[0:1], uuid[1:2], uuid)), "/") | ||||
| input.UploadId = uploadId | input.UploadId = uploadId | ||||
| input.Bucket = setting.Bucket | input.Bucket = setting.Bucket | ||||
| input.PartSize = partSize | input.PartSize = partSize | ||||
| input.Body = partReader | |||||
| _, err := ObsCli.UploadPart(input) | |||||
| input.Body = body | |||||
| output, err := ObsCli.UploadPart(input) | |||||
| if err != nil { | if err != nil { | ||||
| log.Error("UploadPart failed:", err.Error()) | log.Error("UploadPart failed:", err.Error()) | ||||
| return err | |||||
| return "", err | |||||
| } | } | ||||
| return nil | |||||
| return output.ETag, nil | |||||
| } | |||||
| func ObsGenMultiPartSignedUrl(uuid string, uploadId string, partNumber int, partSize int64) (string, error) { | |||||
| /* | |||||
| input := &obs.CreateSignedUrlInput{} | |||||
| input.Bucket = setting.Bucket | |||||
| input.Key = strings.TrimPrefix(path.Join(setting.BasePath, path.Join(uuid[0:1], uuid[1:2], uuid)), "/") | |||||
| input.Expires = int(PresignedUploadPartUrlExpireTime) | |||||
| input.Method = obs.HTTP_PUT | |||||
| input.QueryParams = map[string]string{ | |||||
| "Bucket": input.Bucket, | |||||
| "Key": input.Key, | |||||
| "PartNumber": com.ToStr(partNumber,10), | |||||
| "UploadId": uploadId, | |||||
| "PartSize": com.ToStr(partSize,10), | |||||
| } | |||||
| input.Headers = map[string]string{ | |||||
| } | |||||
| */ | |||||
| Key := strings.TrimPrefix(path.Join(setting.BasePath, path.Join(uuid[0:1], uuid[1:2], uuid)), "/") | |||||
| url, err := ObsCli.CreateUploadPartSignedUrl(setting.Bucket, Key, uploadId, partNumber, partSize) | |||||
| if err != nil { | |||||
| log.Error("CreateSignedUrl failed:", err.Error()) | |||||
| return "", err | |||||
| } | |||||
| log.Info(url) | |||||
| return url, nil | |||||
| } | } | ||||
| @@ -5,6 +5,15 @@ | |||||
| package repo | package repo | ||||
| import ( | import ( | ||||
| contexExt "context" | |||||
| "encoding/json" | |||||
| "errors" | |||||
| "fmt" | |||||
| "mime/multipart" | |||||
| "net/http" | |||||
| "strconv" | |||||
| "strings" | |||||
| "code.gitea.io/gitea/models" | "code.gitea.io/gitea/models" | ||||
| "code.gitea.io/gitea/modules/context" | "code.gitea.io/gitea/modules/context" | ||||
| "code.gitea.io/gitea/modules/log" | "code.gitea.io/gitea/modules/log" | ||||
| @@ -13,13 +22,6 @@ import ( | |||||
| "code.gitea.io/gitea/modules/storage" | "code.gitea.io/gitea/modules/storage" | ||||
| "code.gitea.io/gitea/modules/upload" | "code.gitea.io/gitea/modules/upload" | ||||
| "code.gitea.io/gitea/modules/worker" | "code.gitea.io/gitea/modules/worker" | ||||
| contexExt "context" | |||||
| "encoding/json" | |||||
| "errors" | |||||
| "fmt" | |||||
| "net/http" | |||||
| "strconv" | |||||
| "strings" | |||||
| gouuid "github.com/satori/go.uuid" | gouuid "github.com/satori/go.uuid" | ||||
| ) | ) | ||||
| @@ -38,6 +40,15 @@ type CloudBrainDataset struct { | |||||
| CreateTime string `json:"created_at"` | CreateTime string `json:"created_at"` | ||||
| } | } | ||||
| type UploadForm struct { | |||||
| UploadID string `form:"uploadId"` | |||||
| UuID string `form:"uuid"` | |||||
| PartSize int64 `form:"size"` | |||||
| Offset int64 `form:"offset"` | |||||
| PartNumber int `form:"chunkNumber"` | |||||
| PartFile multipart.File `form:"file"` | |||||
| } | |||||
| func RenderAttachmentSettings(ctx *context.Context) { | func RenderAttachmentSettings(ctx *context.Context) { | ||||
| renderAttachmentSettings(ctx) | renderAttachmentSettings(ctx) | ||||
| } | } | ||||
| @@ -538,15 +549,31 @@ func GetMultipartUploadUrl(ctx *context.Context) { | |||||
| partNumber := ctx.QueryInt("chunkNumber") | partNumber := ctx.QueryInt("chunkNumber") | ||||
| size := ctx.QueryInt64("size") | size := ctx.QueryInt64("size") | ||||
| if size > minio_ext.MinPartSize { | |||||
| ctx.Error(400, fmt.Sprintf("chunk size(%d) is too big", size)) | |||||
| typeCloudBrain := ctx.QueryInt("type") | |||||
| err := checkTypeCloudBrain(typeCloudBrain) | |||||
| if err != nil { | |||||
| ctx.ServerError("checkTypeCloudBrain failed", err) | |||||
| return | return | ||||
| } | } | ||||
| url, err := storage.GenMultiPartSignedUrl(uuid, uploadID, partNumber, size) | |||||
| if err != nil { | |||||
| ctx.Error(500, fmt.Sprintf("GenMultiPartSignedUrl failed: %v", err)) | |||||
| return | |||||
| url := "" | |||||
| if typeCloudBrain == models.TypeCloudBrainOne { | |||||
| if size > minio_ext.MinPartSize { | |||||
| ctx.Error(400, fmt.Sprintf("chunk size(%d) is too big", size)) | |||||
| return | |||||
| } | |||||
| url, err = storage.GenMultiPartSignedUrl(uuid, uploadID, partNumber, size) | |||||
| if err != nil { | |||||
| ctx.Error(500, fmt.Sprintf("GenMultiPartSignedUrl failed: %v", err)) | |||||
| return | |||||
| } | |||||
| } else { | |||||
| url, err = storage.ObsGenMultiPartSignedUrl(uuid, uploadID, partNumber, size) | |||||
| if err != nil { | |||||
| ctx.Error(500, fmt.Sprintf("ObsGenMultiPartSignedUrl failed: %v", err)) | |||||
| return | |||||
| } | |||||
| } | } | ||||
| ctx.JSON(200, map[string]string{ | ctx.JSON(200, map[string]string{ | ||||
| @@ -555,26 +582,34 @@ func GetMultipartUploadUrl(ctx *context.Context) { | |||||
| } | } | ||||
| func UploadPart(ctx *context.Context) { | func UploadPart(ctx *context.Context) { | ||||
| uuid := ctx.Query("uuid") | |||||
| uploadID := ctx.Query("uploadID") | |||||
| partNumber := ctx.QueryInt("chunkNumber") | |||||
| size := ctx.QueryInt64("size") | |||||
| tmp, err := ctx.Req.Body().String() | |||||
| log.Info(tmp) | |||||
| if size > minio_ext.MinPartSize { | |||||
| ctx.Error(400, fmt.Sprintf("chunk size(%d) is too big", size)) | |||||
| err = ctx.Req.ParseMultipartForm(100*1024*1024) | |||||
| if err != nil { | |||||
| ctx.Error(http.StatusBadRequest, fmt.Sprintf("ParseMultipartForm failed: %v", err)) | |||||
| return | return | ||||
| } | } | ||||
| url, err := storage.GenMultiPartSignedUrl(uuid, uploadID, partNumber, size) | |||||
| //todo:get file reader | |||||
| //err := storage.ObsUploadPart(uuid, uploadID, partNumber, size, partReader) | |||||
| file, fileHeader, err := ctx.Req.FormFile("file") | |||||
| log.Info(ctx.Req.Form.Get("file")) | |||||
| if err != nil { | if err != nil { | ||||
| ctx.Error(500, fmt.Sprintf("GenMultiPartSignedUrl failed: %v", err)) | |||||
| ctx.Error(http.StatusBadRequest, fmt.Sprintf("FormFile failed: %v", err)) | |||||
| return | |||||
| } | |||||
| log.Info(fileHeader.Filename) | |||||
| etag, err := storage.ObsUploadPart("", "", 1, 1, file) | |||||
| if err != nil { | |||||
| ctx.Error(500, fmt.Sprintf("ObsUploadPart failed: %v", err)) | |||||
| return | return | ||||
| } | } | ||||
| ctx.JSON(200, map[string]string{ | ctx.JSON(200, map[string]string{ | ||||
| "url": url, | |||||
| "etag": etag, | |||||
| }) | }) | ||||
| } | } | ||||
| @@ -21,7 +21,7 @@ import qs from 'qs'; | |||||
| import createDropzone from '../features/dropzone.js'; | import createDropzone from '../features/dropzone.js'; | ||||
| const {_AppSubUrl, _StaticUrlPrefix, csrf} = window.config; | const {_AppSubUrl, _StaticUrlPrefix, csrf} = window.config; | ||||
| const cloud_brain_type = 0; | |||||
| const cloud_brain_type = 1; | |||||
| export default { | export default { | ||||
| data() { | data() { | ||||
| @@ -129,9 +129,9 @@ export default { | |||||
| finishUpload(file) { | finishUpload(file) { | ||||
| this.emitDropzoneSuccess(file); | this.emitDropzoneSuccess(file); | ||||
| setTimeout(() => { | |||||
| window.location.reload(); | |||||
| }, 1000); | |||||
| // setTimeout(() => { | |||||
| // window.location.reload(); | |||||
| // }, 1000); | |||||
| }, | }, | ||||
| computeMD5(file) { | computeMD5(file) { | ||||
| @@ -326,6 +326,7 @@ export default { | |||||
| uploadID: file.uploadID, | uploadID: file.uploadID, | ||||
| size: partSize, | size: partSize, | ||||
| chunkNumber: currentChunk + 1, | chunkNumber: currentChunk + 1, | ||||
| type: cloud_brain_type, | |||||
| _csrf: csrf | _csrf: csrf | ||||
| } | } | ||||
| }); | }); | ||||
| @@ -348,30 +349,49 @@ export default { | |||||
| }) | }) | ||||
| ); | ); | ||||
| } | } | ||||
| async function uploadPart(currentChunk, partSize, e) { | |||||
| console.log(e); | |||||
| let params = new FormData(); | |||||
| params.append("uuid", file.uuid); | |||||
| params.append("uploadId", file.uploadID); | |||||
| params.append("size", partSize); | |||||
| params.append("chunkNumber", currentChunk + 1); | |||||
| params.append("file", e.target.file); | |||||
| params.append("_csrf", csrf); | |||||
| return await axios.post('/attachments/upload_part', | |||||
| params, | |||||
| {headers: {'Content-Type': 'multipart/form-data'}} | |||||
| ); | |||||
| } | |||||
| async function uploadChunk(e) { | async function uploadChunk(e) { | ||||
| try { | try { | ||||
| if (!checkSuccessChunks()) { | if (!checkSuccessChunks()) { | ||||
| const start = currentChunk * chunkSize; | const start = currentChunk * chunkSize; | ||||
| const partSize = | const partSize = | ||||
| start + chunkSize >= file.size ? file.size - start : chunkSize; | start + chunkSize >= file.size ? file.size - start : chunkSize; | ||||
| await uploadPart(currentChunk, partSize, e); | |||||
| // 获取分片上传url | // 获取分片上传url | ||||
| await getUploadChunkUrl(currentChunk, partSize); | |||||
| if (urls[currentChunk] != '') { | |||||
| // 上传到minio | |||||
| await uploadMinio(urls[currentChunk], e); | |||||
| if (etags[currentChunk] != '') { | |||||
| // 更新数据库:分片上传结果 | |||||
| //await updateChunk(currentChunk); | |||||
| } else { | |||||
| console.log("上传到minio uploadChunk etags[currentChunk] == ''");// TODO | |||||
| } | |||||
| } else { | |||||
| console.log("uploadChunk urls[currentChunk] != ''");// TODO | |||||
| } | |||||
| // await getUploadChunkUrl(currentChunk, partSize); | |||||
| // if (urls[currentChunk] != '') { | |||||
| // // 上传到minio | |||||
| // await uploadMinio(urls[currentChunk], e); | |||||
| // if (etags[currentChunk] != '') { | |||||
| // // 更新数据库:分片上传结果 | |||||
| // //await updateChunk(currentChunk); | |||||
| // } else { | |||||
| // console.log("上传到minio uploadChunk etags[currentChunk] == ''");// TODO | |||||
| // } | |||||
| // } else { | |||||
| // console.log("uploadChunk urls[currentChunk] != ''");// TODO | |||||
| // } | |||||
| } | } | ||||
| } catch (error) { | } catch (error) { | ||||
| this.emitDropzoneFailed(file); | |||||
| console.log(error); | console.log(error); | ||||
| this.emitDropzoneFailed(file); | |||||
| } | } | ||||
| } | } | ||||