| @@ -41,6 +41,7 @@ type Attachment struct { | |||||
| Size int64 `xorm:"DEFAULT 0"` | Size int64 `xorm:"DEFAULT 0"` | ||||
| IsPrivate bool `xorm:"DEFAULT false"` | IsPrivate bool `xorm:"DEFAULT false"` | ||||
| DecompressState int32 `xorm:"DEFAULT 0"` | DecompressState int32 `xorm:"DEFAULT 0"` | ||||
| Type int `xorm:"DEFAULT 0"` | |||||
| CreatedUnix timeutil.TimeStamp `xorm:"created"` | CreatedUnix timeutil.TimeStamp `xorm:"created"` | ||||
| } | } | ||||
| @@ -350,7 +351,7 @@ func GetUnDecompressAttachments() ([]*Attachment, error) { | |||||
| func getUnDecompressAttachments(e Engine) ([]*Attachment, error) { | func getUnDecompressAttachments(e Engine) ([]*Attachment, error) { | ||||
| attachments := make([]*Attachment, 0, 10) | attachments := make([]*Attachment, 0, 10) | ||||
| return attachments, e.Where("decompress_state = ? and dataset_id != 0 and name like '%.zip'", DecompressStateInit).Find(&attachments) | |||||
| return attachments, e.Where("decompress_state = ? and dataset_id != 0 and type = ? and name like '%.zip'", DecompressStateInit, TypeCloudBrainOne).Find(&attachments) | |||||
| } | } | ||||
| func GetAllPublicAttachments() ([]*AttachmentUsername, error) { | func GetAllPublicAttachments() ([]*AttachmentUsername, error) { | ||||
| @@ -360,7 +361,7 @@ func GetAllPublicAttachments() ([]*AttachmentUsername, error) { | |||||
| func getAllPublicAttachments(e Engine) ([]*AttachmentUsername, error) { | func getAllPublicAttachments(e Engine) ([]*AttachmentUsername, error) { | ||||
| attachments := make([]*AttachmentUsername, 0, 10) | attachments := make([]*AttachmentUsername, 0, 10) | ||||
| if err := e.Table("attachment").Join("LEFT", "`user`", "attachment.uploader_id "+ | if err := e.Table("attachment").Join("LEFT", "`user`", "attachment.uploader_id "+ | ||||
| "= `user`.id").Where("decompress_state= ? and is_private= ?", DecompressStateDone, false).Find(&attachments); err != nil { | |||||
| "= `user`.id").Where("decompress_state= ? and is_private= ? and type = ?", DecompressStateDone, false, TypeCloudBrainOne).Find(&attachments); err != nil { | |||||
| return nil, err | return nil, err | ||||
| } | } | ||||
| return attachments, nil | return attachments, nil | ||||
| @@ -378,7 +379,7 @@ func GetPrivateAttachments(username string) ([]*AttachmentUsername, error) { | |||||
| func getPrivateAttachments(e Engine, userID int64) ([]*AttachmentUsername, error) { | func getPrivateAttachments(e Engine, userID int64) ([]*AttachmentUsername, error) { | ||||
| attachments := make([]*AttachmentUsername, 0, 10) | attachments := make([]*AttachmentUsername, 0, 10) | ||||
| if err := e.Table("attachment").Join("LEFT", "`user`", "attachment.uploader_id "+ | if err := e.Table("attachment").Join("LEFT", "`user`", "attachment.uploader_id "+ | ||||
| "= `user`.id").Where("decompress_state= ? and uploader_id= ?", DecompressStateDone, userID).Find(&attachments); err != nil { | |||||
| "= `user`.id").Where("decompress_state= ? and uploader_id= ? and type = ?", DecompressStateDone, userID, TypeCloudBrainOne).Find(&attachments); err != nil { | |||||
| return nil, err | return nil, err | ||||
| } | } | ||||
| return attachments, nil | return attachments, nil | ||||
| @@ -406,7 +407,7 @@ func GetAllUserAttachments(userID int64) ([]*AttachmentUsername, error) { | |||||
| func getAllUserAttachments(e Engine, userID int64) ([]*AttachmentUsername, error) { | func getAllUserAttachments(e Engine, userID int64) ([]*AttachmentUsername, error) { | ||||
| attachments := make([]*AttachmentUsername, 0, 10) | attachments := make([]*AttachmentUsername, 0, 10) | ||||
| if err := e.Table("attachment").Join("LEFT", "`user`", "attachment.uploader_id "+ | if err := e.Table("attachment").Join("LEFT", "`user`", "attachment.uploader_id "+ | ||||
| "= `user`.id").Where("decompress_state= ? and (uploader_id= ? or is_private = ?)", DecompressStateDone, userID, false).Find(&attachments); err != nil { | |||||
| "= `user`.id").Where("decompress_state= ? and type = ? and (uploader_id= ? or is_private = ?)", DecompressStateDone, TypeCloudBrainOne, userID, false).Find(&attachments); err != nil { | |||||
| return nil, err | return nil, err | ||||
| } | } | ||||
| return attachments, nil | return attachments, nil | ||||
| @@ -10,6 +10,11 @@ const ( | |||||
| FileUploaded | FileUploaded | ||||
| ) | ) | ||||
| const ( | |||||
| TypeCloudBrainOne = 0 | |||||
| TypeCloudBrainTwo = 1 | |||||
| ) | |||||
| type FileChunk struct { | type FileChunk struct { | ||||
| ID int64 `xorm:"pk autoincr"` | ID int64 `xorm:"pk autoincr"` | ||||
| UUID string `xorm:"uuid UNIQUE"` | UUID string `xorm:"uuid UNIQUE"` | ||||
| @@ -19,7 +24,8 @@ type FileChunk struct { | |||||
| TotalChunks int | TotalChunks int | ||||
| Size int64 | Size int64 | ||||
| UserID int64 `xorm:"INDEX"` | UserID int64 `xorm:"INDEX"` | ||||
| CompletedParts []string `xorm:"DEFAULT """` // chunkNumber+etag eg: ,1-asqwewqe21312312.2-123hjkas | |||||
| Type int `xorm:"INDEX DEFAULT 0"` | |||||
| CompletedParts []string `xorm:"DEFAULT ''"` // chunkNumber+etag eg: ,1-asqwewqe21312312.2-123hjkas | |||||
| CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` | CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` | ||||
| UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` | UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` | ||||
| } | } | ||||
| @@ -41,14 +47,14 @@ func getFileChunkByMD5(e Engine, md5 string) (*FileChunk, error) { | |||||
| } | } | ||||
| // GetFileChunkByMD5 returns fileChunk by given id | // GetFileChunkByMD5 returns fileChunk by given id | ||||
| func GetFileChunkByMD5AndUser(md5 string, userID int64) (*FileChunk, error) { | |||||
| return getFileChunkByMD5AndUser(x, md5, userID) | |||||
| func GetFileChunkByMD5AndUser(md5 string, userID int64, typeCloudBrain int) (*FileChunk, error) { | |||||
| return getFileChunkByMD5AndUser(x, md5, userID, typeCloudBrain) | |||||
| } | } | ||||
| func getFileChunkByMD5AndUser(e Engine, md5 string, userID int64) (*FileChunk, error) { | |||||
| func getFileChunkByMD5AndUser(e Engine, md5 string, userID int64, typeCloudBrain int) (*FileChunk, error) { | |||||
| fileChunk := new(FileChunk) | fileChunk := new(FileChunk) | ||||
| if has, err := e.Where("md5 = ? and user_id = ?", md5, userID).Get(fileChunk); err != nil { | |||||
| if has, err := e.Where("md5 = ? and user_id = ? and type = ?", md5, userID, typeCloudBrain).Get(fileChunk); err != nil { | |||||
| return nil, err | return nil, err | ||||
| } else if !has { | } else if !has { | ||||
| return nil, ErrFileChunkNotExist{md5, ""} | return nil, ErrFileChunkNotExist{md5, ""} | ||||
| @@ -89,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 | |||||
| } | |||||
| @@ -447,6 +447,15 @@ var ( | |||||
| //blockchain config | //blockchain config | ||||
| BlockChainHost string | BlockChainHost string | ||||
| CommitValidDate string | CommitValidDate string | ||||
| //obs config | |||||
| Endpoint string | |||||
| AccessKeyID string | |||||
| SecretAccessKey string | |||||
| Bucket string | |||||
| Location string | |||||
| BasePath string | |||||
| //RealPath string | |||||
| ) | ) | ||||
| // DateLang transforms standard language locale name to corresponding value in datetime plugin. | // DateLang transforms standard language locale name to corresponding value in datetime plugin. | ||||
| @@ -1131,6 +1140,14 @@ func NewContext() { | |||||
| sec = Cfg.Section("blockchain") | sec = Cfg.Section("blockchain") | ||||
| BlockChainHost = sec.Key("HOST").MustString("http://192.168.136.66:3302/") | BlockChainHost = sec.Key("HOST").MustString("http://192.168.136.66:3302/") | ||||
| CommitValidDate = sec.Key("COMMIT_VALID_DATE").MustString("2021-01-15") | CommitValidDate = sec.Key("COMMIT_VALID_DATE").MustString("2021-01-15") | ||||
| sec = Cfg.Section("obs") | |||||
| Endpoint = sec.Key("ENDPOINT").MustString("112.95.163.82") | |||||
| AccessKeyID = sec.Key("ACCESS_KEY_ID").MustString("") | |||||
| SecretAccessKey = sec.Key("SECRET_ACCESS_KEY").MustString("") | |||||
| Bucket = sec.Key("BUCKET").MustString("testopendata") | |||||
| Location = sec.Key("LOCATION").MustString("cn-south-222") | |||||
| BasePath = sec.Key("BASE_PATH").MustString("attachment/") | |||||
| } | } | ||||
| func loadInternalToken(sec *ini.Section) string { | func loadInternalToken(sec *ini.Section) string { | ||||
| @@ -0,0 +1,153 @@ | |||||
| // Copyright 2020 The Gitea Authors. All rights reserved. | |||||
| // Use of this source code is governed by a MIT-style | |||||
| // license that can be found in the LICENSE file. | |||||
| package storage | |||||
| import ( | |||||
| "io" | |||||
| "path" | |||||
| "strconv" | |||||
| "strings" | |||||
| "code.gitea.io/gitea/modules/log" | |||||
| "code.gitea.io/gitea/modules/obs" | |||||
| "code.gitea.io/gitea/modules/setting" | |||||
| ) | |||||
| //check if has the object | |||||
| func ObsHasObject(path string) (bool, error) { | |||||
| hasObject := false | |||||
| output, err := ObsCli.ListObjects(&obs.ListObjectsInput{Bucket:setting.Bucket}) | |||||
| if err != nil { | |||||
| log.Error("ListObjects failed:%v", err) | |||||
| return hasObject, err | |||||
| } | |||||
| for _, obj := range output.Contents { | |||||
| if path == obj.Key { | |||||
| hasObject = true | |||||
| break | |||||
| } | |||||
| } | |||||
| return hasObject, nil | |||||
| } | |||||
| func GetObsPartInfos(uuid string, uploadID string) (string, error) { | |||||
| key := strings.TrimPrefix(path.Join(setting.BasePath, path.Join(uuid[0:1], uuid[1:2], uuid)), "/") | |||||
| output, err := ObsCli.ListParts(&obs.ListPartsInput{ | |||||
| Bucket: setting.Bucket, | |||||
| Key: key, | |||||
| UploadId: uploadID, | |||||
| }) | |||||
| if err != nil { | |||||
| log.Error("ListParts failed:", err.Error()) | |||||
| return "", err | |||||
| } | |||||
| var chunks string | |||||
| for _, partInfo := range output.Parts { | |||||
| chunks += strconv.Itoa(partInfo.PartNumber) + "-" + partInfo.ETag + "," | |||||
| } | |||||
| return chunks, nil | |||||
| } | |||||
| func NewObsMultiPartUpload(uuid string) (string, error) { | |||||
| input := &obs.InitiateMultipartUploadInput{} | |||||
| input.Bucket = setting.Bucket | |||||
| input.Key = strings.TrimPrefix(path.Join(setting.BasePath, path.Join(uuid[0:1], uuid[1:2], uuid)), "/") | |||||
| output, err := ObsCli.InitiateMultipartUpload(input) | |||||
| if err != nil { | |||||
| log.Error("InitiateMultipartUpload failed:", err.Error()) | |||||
| return "", err | |||||
| } | |||||
| return output.UploadId, nil | |||||
| } | |||||
| func CompleteObsMultiPartUpload(uuid string, uploadID string) error { | |||||
| input := &obs.CompleteMultipartUploadInput{} | |||||
| input.Bucket = setting.Bucket | |||||
| input.Key = strings.TrimPrefix(path.Join(setting.BasePath, path.Join(uuid[0:1], uuid[1:2], uuid)), "/") | |||||
| input.UploadId = uploadID | |||||
| output, err := ObsCli.ListParts(&obs.ListPartsInput{ | |||||
| Bucket: setting.Bucket, | |||||
| Key: input.Key, | |||||
| UploadId: uploadID, | |||||
| }) | |||||
| if err != nil { | |||||
| log.Error("ListParts failed:", err.Error()) | |||||
| return err | |||||
| } | |||||
| for _, partInfo := range output.Parts { | |||||
| input.Parts = append(input.Parts, obs.Part{ | |||||
| PartNumber: partInfo.PartNumber, | |||||
| ETag: partInfo.ETag, | |||||
| }) | |||||
| } | |||||
| _, err = ObsCli.CompleteMultipartUpload(input) | |||||
| if err != nil { | |||||
| log.Error("CompleteMultipartUpload failed:", err.Error()) | |||||
| return err | |||||
| } | |||||
| return nil | |||||
| } | |||||
| func ObsUploadPart(uuid string, uploadId string, partNumber int, partSize int64, body io.Reader) (string, error) { | |||||
| input := &obs.UploadPartInput{} | |||||
| input.PartNumber = partNumber | |||||
| input.Key = strings.TrimPrefix(path.Join(setting.BasePath, path.Join(uuid[0:1], uuid[1:2], uuid)), "/") | |||||
| input.UploadId = uploadId | |||||
| input.Bucket = setting.Bucket | |||||
| input.PartSize = partSize | |||||
| input.Body = body | |||||
| output, err := ObsCli.UploadPart(input) | |||||
| if err != nil { | |||||
| log.Error("UploadPart failed:", err.Error()) | |||||
| return "", err | |||||
| } | |||||
| 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 | |||||
| } | |||||
| @@ -8,6 +8,8 @@ import ( | |||||
| "fmt" | "fmt" | ||||
| "io" | "io" | ||||
| "code.gitea.io/gitea/modules/log" | |||||
| "code.gitea.io/gitea/modules/obs" | |||||
| "code.gitea.io/gitea/modules/setting" | "code.gitea.io/gitea/modules/setting" | ||||
| ) | ) | ||||
| @@ -40,6 +42,7 @@ func Copy(dstStorage ObjectStorage, dstPath string, srcStorage ObjectStorage, sr | |||||
| var ( | var ( | ||||
| // Attachments represents attachments storage | // Attachments represents attachments storage | ||||
| Attachments ObjectStorage | Attachments ObjectStorage | ||||
| ObsCli *obs.ObsClient | |||||
| ) | ) | ||||
| // Init init the stoarge | // Init init the stoarge | ||||
| @@ -63,6 +66,12 @@ func Init() error { | |||||
| return fmt.Errorf("Unsupported attachment store type: %s", setting.Attachment.StoreType) | return fmt.Errorf("Unsupported attachment store type: %s", setting.Attachment.StoreType) | ||||
| } | } | ||||
| ObsCli, err = obs.New(setting.AccessKeyID, setting.SecretAccessKey, setting.Endpoint) | |||||
| if err != nil { | |||||
| log.Error("obs.New failed:", err) | |||||
| return err | |||||
| } | |||||
| if err != nil { | if err != nil { | ||||
| return err | return err | ||||
| } | } | ||||
| @@ -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,12 +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" | |||||
| "fmt" | |||||
| "net/http" | |||||
| "strconv" | |||||
| "strings" | |||||
| gouuid "github.com/satori/go.uuid" | gouuid "github.com/satori/go.uuid" | ||||
| ) | ) | ||||
| @@ -37,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) | ||||
| } | } | ||||
| @@ -340,9 +352,16 @@ func UpdateAttachmentDecompressState(ctx *context.Context) { | |||||
| func GetSuccessChunks(ctx *context.Context) { | func GetSuccessChunks(ctx *context.Context) { | ||||
| fileMD5 := ctx.Query("md5") | fileMD5 := ctx.Query("md5") | ||||
| typeCloudBrain := ctx.QueryInt("type") | |||||
| var chunks string | var chunks string | ||||
| fileChunk, err := models.GetFileChunkByMD5AndUser(fileMD5, ctx.User.ID) | |||||
| err := checkTypeCloudBrain(typeCloudBrain) | |||||
| if err != nil { | |||||
| ctx.ServerError("checkTypeCloudBrain failed", err) | |||||
| return | |||||
| } | |||||
| fileChunk, err := models.GetFileChunkByMD5AndUser(fileMD5, ctx.User.ID, typeCloudBrain) | |||||
| if err != nil { | if err != nil { | ||||
| if models.IsErrFileChunkNotExist(err) { | if models.IsErrFileChunkNotExist(err) { | ||||
| ctx.JSON(200, map[string]string{ | ctx.JSON(200, map[string]string{ | ||||
| @@ -357,12 +376,22 @@ func GetSuccessChunks(ctx *context.Context) { | |||||
| return | return | ||||
| } | } | ||||
| isExist, err := storage.Attachments.HasObject(models.AttachmentRelativePath(fileChunk.UUID)) | |||||
| if err != nil { | |||||
| ctx.ServerError("HasObject failed", err) | |||||
| return | |||||
| isExist := false | |||||
| if typeCloudBrain == models.TypeCloudBrainOne { | |||||
| isExist, err = storage.Attachments.HasObject(models.AttachmentRelativePath(fileChunk.UUID)) | |||||
| if err != nil { | |||||
| ctx.ServerError("HasObject failed", err) | |||||
| return | |||||
| } | |||||
| } else { | |||||
| isExist, err = storage.ObsHasObject(models.AttachmentRelativePath(fileChunk.UUID)) | |||||
| if err != nil { | |||||
| ctx.ServerError("ObsHasObject failed", err) | |||||
| return | |||||
| } | |||||
| } | } | ||||
| if isExist { | if isExist { | ||||
| if fileChunk.IsUploaded == models.FileNotUploaded { | if fileChunk.IsUploaded == models.FileNotUploaded { | ||||
| log.Info("the file has been uploaded but not recorded") | log.Info("the file has been uploaded but not recorded") | ||||
| @@ -380,10 +409,18 @@ func GetSuccessChunks(ctx *context.Context) { | |||||
| } | } | ||||
| } | } | ||||
| chunks, err = storage.GetPartInfos(fileChunk.UUID, fileChunk.UploadID) | |||||
| if err != nil { | |||||
| ctx.ServerError("GetPartInfos failed", err) | |||||
| return | |||||
| if typeCloudBrain == models.TypeCloudBrainOne { | |||||
| chunks, err = storage.GetPartInfos(fileChunk.UUID, fileChunk.UploadID) | |||||
| if err != nil { | |||||
| ctx.ServerError("GetPartInfos failed", err) | |||||
| return | |||||
| } | |||||
| } else { | |||||
| chunks, err = storage.GetObsPartInfos(fileChunk.UUID, fileChunk.UploadID) | |||||
| if err != nil { | |||||
| ctx.ServerError("GetObsPartInfos failed", err) | |||||
| return | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| @@ -445,6 +482,13 @@ func NewMultipart(ctx *context.Context) { | |||||
| return | return | ||||
| } | } | ||||
| typeCloudBrain := ctx.QueryInt("type") | |||||
| err = checkTypeCloudBrain(typeCloudBrain) | |||||
| if err != nil { | |||||
| ctx.ServerError("checkTypeCloudBrain failed", err) | |||||
| return | |||||
| } | |||||
| if setting.Attachment.StoreType == storage.MinioStorageType { | if setting.Attachment.StoreType == storage.MinioStorageType { | ||||
| totalChunkCounts := ctx.QueryInt("totalChunkCounts") | totalChunkCounts := ctx.QueryInt("totalChunkCounts") | ||||
| if totalChunkCounts > minio_ext.MaxPartsCount { | if totalChunkCounts > minio_ext.MaxPartsCount { | ||||
| @@ -459,10 +503,19 @@ func NewMultipart(ctx *context.Context) { | |||||
| } | } | ||||
| uuid := gouuid.NewV4().String() | uuid := gouuid.NewV4().String() | ||||
| uploadID, err := storage.NewMultiPartUpload(uuid) | |||||
| if err != nil { | |||||
| ctx.ServerError("NewMultipart", err) | |||||
| return | |||||
| var uploadID string | |||||
| if typeCloudBrain == models.TypeCloudBrainOne { | |||||
| uploadID, err = storage.NewMultiPartUpload(uuid) | |||||
| if err != nil { | |||||
| ctx.ServerError("NewMultipart", err) | |||||
| return | |||||
| } | |||||
| } else { | |||||
| uploadID, err = storage.NewObsMultiPartUpload(uuid) | |||||
| if err != nil { | |||||
| ctx.ServerError("NewObsMultiPartUpload", err) | |||||
| return | |||||
| } | |||||
| } | } | ||||
| _, err = models.InsertFileChunk(&models.FileChunk{ | _, err = models.InsertFileChunk(&models.FileChunk{ | ||||
| @@ -472,6 +525,7 @@ func NewMultipart(ctx *context.Context) { | |||||
| Md5: ctx.Query("md5"), | Md5: ctx.Query("md5"), | ||||
| Size: fileSize, | Size: fileSize, | ||||
| TotalChunks: totalChunkCounts, | TotalChunks: totalChunkCounts, | ||||
| Type: typeCloudBrain, | |||||
| }) | }) | ||||
| if err != nil { | if err != nil { | ||||
| @@ -495,25 +549,80 @@ 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) | |||||
| 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{ | |||||
| "url": url, | |||||
| }) | |||||
| } | |||||
| func UploadPart(ctx *context.Context) { | |||||
| tmp, err := ctx.Req.Body().String() | |||||
| log.Info(tmp) | |||||
| err = ctx.Req.ParseMultipartForm(100*1024*1024) | |||||
| if err != nil { | if err != nil { | ||||
| ctx.Error(500, fmt.Sprintf("GenMultiPartSignedUrl failed: %v", err)) | |||||
| ctx.Error(http.StatusBadRequest, fmt.Sprintf("ParseMultipartForm failed: %v", err)) | |||||
| return | |||||
| } | |||||
| file, fileHeader, err := ctx.Req.FormFile("file") | |||||
| log.Info(ctx.Req.Form.Get("file")) | |||||
| if err != nil { | |||||
| 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, | |||||
| }) | }) | ||||
| } | } | ||||
| func CompleteMultipart(ctx *context.Context) { | func CompleteMultipart(ctx *context.Context) { | ||||
| uuid := ctx.Query("uuid") | uuid := ctx.Query("uuid") | ||||
| uploadID := ctx.Query("uploadID") | uploadID := ctx.Query("uploadID") | ||||
| typeCloudBrain := ctx.QueryInt("type") | |||||
| err := checkTypeCloudBrain(typeCloudBrain) | |||||
| if err != nil { | |||||
| ctx.ServerError("checkTypeCloudBrain failed", err) | |||||
| return | |||||
| } | |||||
| fileChunk, err := models.GetFileChunkByUUID(uuid) | fileChunk, err := models.GetFileChunkByUUID(uuid) | ||||
| if err != nil { | if err != nil { | ||||
| @@ -525,10 +634,18 @@ func CompleteMultipart(ctx *context.Context) { | |||||
| return | return | ||||
| } | } | ||||
| _, err = storage.CompleteMultiPartUpload(uuid, uploadID) | |||||
| if err != nil { | |||||
| ctx.Error(500, fmt.Sprintf("CompleteMultiPartUpload failed: %v", err)) | |||||
| return | |||||
| if typeCloudBrain == models.TypeCloudBrainOne { | |||||
| _, err = storage.CompleteMultiPartUpload(uuid, uploadID) | |||||
| if err != nil { | |||||
| ctx.Error(500, fmt.Sprintf("CompleteMultiPartUpload failed: %v", err)) | |||||
| return | |||||
| } | |||||
| } else { | |||||
| err = storage.CompleteObsMultiPartUpload(uuid, uploadID) | |||||
| if err != nil { | |||||
| ctx.Error(500, fmt.Sprintf("CompleteObsMultiPartUpload failed: %v", err)) | |||||
| return | |||||
| } | |||||
| } | } | ||||
| fileChunk.IsUploaded = models.FileUploaded | fileChunk.IsUploaded = models.FileUploaded | ||||
| @@ -546,6 +663,7 @@ func CompleteMultipart(ctx *context.Context) { | |||||
| Name: ctx.Query("file_name"), | Name: ctx.Query("file_name"), | ||||
| Size: ctx.QueryInt64("size"), | Size: ctx.QueryInt64("size"), | ||||
| DatasetID: ctx.QueryInt64("dataset_id"), | DatasetID: ctx.QueryInt64("dataset_id"), | ||||
| Type: typeCloudBrain, | |||||
| }) | }) | ||||
| if err != nil { | if err != nil { | ||||
| @@ -704,3 +822,11 @@ func queryDatasets(ctx *context.Context, attachs []*models.AttachmentUsername) { | |||||
| }) | }) | ||||
| return | return | ||||
| } | } | ||||
| func checkTypeCloudBrain(typeCloudBrain int) error { | |||||
| if typeCloudBrain != models.TypeCloudBrainOne && typeCloudBrain != models.TypeCloudBrainTwo { | |||||
| log.Error("type error:", typeCloudBrain) | |||||
| return errors.New("type error") | |||||
| } | |||||
| return nil | |||||
| } | |||||
| @@ -529,6 +529,7 @@ func RegisterRoutes(m *macaron.Macaron) { | |||||
| m.Get("/get_multipart_url", repo.GetMultipartUploadUrl) | m.Get("/get_multipart_url", repo.GetMultipartUploadUrl) | ||||
| m.Post("/complete_multipart", repo.CompleteMultipart) | m.Post("/complete_multipart", repo.CompleteMultipart) | ||||
| m.Post("/update_chunk", repo.UpdateMultipart) | m.Post("/update_chunk", repo.UpdateMultipart) | ||||
| m.Post("/upload_part", repo.UploadPart) | |||||
| }, reqSignIn) | }, reqSignIn) | ||||
| m.Group("/attachments", func() { | m.Group("/attachments", func() { | ||||
| @@ -3,13 +3,13 @@ | |||||
| {{template "repo/header" .}} | {{template "repo/header" .}} | ||||
| <div class="ui container"> | <div class="ui container"> | ||||
| {{template "base/alert" .}} | {{template "base/alert" .}} | ||||
| <div class="ui repo-description"> | |||||
| <div id="repo-desc"> | |||||
| <div class="ui repo-description stackable grid"> | |||||
| <div id="repo-desc" class="ui twelve wide column"> | |||||
| {{if .Repository.DescriptionHTML}}<span class="description">{{.Repository.DescriptionHTML}}</span>{{else if .IsRepositoryAdmin}}<span class="no-description text-italic">{{.i18n.Tr "repo.no_desc"}}</span>{{end}} | {{if .Repository.DescriptionHTML}}<span class="description">{{.Repository.DescriptionHTML}}</span>{{else if .IsRepositoryAdmin}}<span class="no-description text-italic">{{.i18n.Tr "repo.no_desc"}}</span>{{end}} | ||||
| <a class="link" href="{{.Repository.Website}}">{{.Repository.Website}}</a> | <a class="link" href="{{.Repository.Website}}">{{.Repository.Website}}</a> | ||||
| </div> | </div> | ||||
| {{if .RepoSearchEnabled}} | {{if .RepoSearchEnabled}} | ||||
| <div class="ui repo-search"> | |||||
| <div class="ui repo-search four wide column"> | |||||
| <form class="ui form ignore-dirty" action="{{.RepoLink}}/search" method="get"> | <form class="ui form ignore-dirty" action="{{.RepoLink}}/search" method="get"> | ||||
| <div class="field"> | <div class="field"> | ||||
| <div class="ui action input"> | <div class="ui action input"> | ||||
| @@ -13,9 +13,11 @@ | |||||
| <div class="item"> | <div class="item"> | ||||
| <a class="ui" href="#">{{svg "octicon-database" 16}} <b>{{SizeFmt .Repository.Size}}</b></a> | <a class="ui" href="#">{{svg "octicon-database" 16}} <b>{{SizeFmt .Repository.Size}}</b></a> | ||||
| </div> | </div> | ||||
| {{if (not .IsPrivate)}} | |||||
| <div class="item"> | <div class="item"> | ||||
| <a class="ui" href="#">{{svg "octicon-desktop-download" 16}} <b>{{.Repository.CloneCnt}} {{.i18n.Tr "repo.clone_cnt" }}</b></a> | <a class="ui" href="#">{{svg "octicon-desktop-download" 16}} <b>{{.Repository.CloneCnt}} {{.i18n.Tr "repo.clone_cnt" }}</b></a> | ||||
| </div> | </div> | ||||
| {{end}} | |||||
| {{end}} | {{end}} | ||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| @@ -21,6 +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 = 1; | |||||
| export default { | export default { | ||||
| data() { | data() { | ||||
| @@ -128,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) { | ||||
| @@ -255,6 +256,7 @@ export default { | |||||
| const params = { | const params = { | ||||
| params: { | params: { | ||||
| md5: file.uniqueIdentifier, | md5: file.uniqueIdentifier, | ||||
| type: cloud_brain_type, | |||||
| _csrf: csrf | _csrf: csrf | ||||
| } | } | ||||
| }; | }; | ||||
| @@ -283,6 +285,7 @@ export default { | |||||
| md5: file.uniqueIdentifier, | md5: file.uniqueIdentifier, | ||||
| size: file.size, | size: file.size, | ||||
| fileType: file.type, | fileType: file.type, | ||||
| type: cloud_brain_type, | |||||
| _csrf: csrf | _csrf: csrf | ||||
| } | } | ||||
| }); | }); | ||||
| @@ -323,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 | ||||
| } | } | ||||
| }); | }); | ||||
| @@ -345,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); | |||||
| } | } | ||||
| } | } | ||||
| @@ -381,6 +404,7 @@ export default { | |||||
| file_name: file.name, | file_name: file.name, | ||||
| size: file.size, | size: file.size, | ||||
| dataset_id: file.datasetId, | dataset_id: file.datasetId, | ||||
| type: cloud_brain_type, | |||||
| _csrf: csrf | _csrf: csrf | ||||
| }) | }) | ||||
| ); | ); | ||||