@@ -1069,3 +1069,14 @@ PASSWORD = 4BPmgvK2hb2Eywwyp4YZRY4B7yQf4DAC | |||||
[blockchain] | [blockchain] | ||||
HOST = http://192.168.207.84:3002/ | HOST = http://192.168.207.84:3002/ | ||||
COMMIT_VALID_DATE = 2021-01-15 | COMMIT_VALID_DATE = 2021-01-15 | ||||
[obs] | |||||
ENDPOINT = 112.95.163.82 | |||||
ACCESS_KEY_ID = FDP3LRMHLB9S77VWEHE3 | |||||
SECRET_ACCESS_KEY = LyM82Wk80pgjhs2z7AdDcsdpCWhbsJtSzQ7hkESN | |||||
BUCKET = testopendata | |||||
LOCATION = cn-south-222 | |||||
BASE_PATH = attachment/ | |||||
[modelarts] | |||||
ENDPOINT = 112.95.163.80 |
@@ -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 | ||||
}) | }) | ||||
); | ); | ||||