Browse Source

Merge pull request 'upload to minio directly' (#18) from presignedUrl into develop

Reviewed-by: berry <senluowanxiangt@gmail.com>
master
berry 5 years ago
parent
commit
263b818c72
10 changed files with 167 additions and 17 deletions
  1. +10
    -0
      models/attachment.go
  2. +10
    -2
      modules/storage/local.go
  3. +42
    -7
      modules/storage/minio.go
  4. +2
    -0
      modules/storage/storage.go
  5. +4
    -0
      modules/upload/filetype.go
  6. +65
    -0
      routers/repo/attachment.go
  7. +17
    -0
      routers/repo/dataset.go
  8. +2
    -0
      routers/routes/routes.go
  9. +1
    -1
      templates/repo/datasets/dataset.tmpl
  10. +14
    -7
      web_src/js/index.js

+ 10
- 0
models/attachment.go View File

@@ -318,3 +318,13 @@ func (a *Attachment) LinkedDataSet() (*Dataset, error) {
} }
return nil, nil return nil, nil
} }

// InsertAttachment insert a record into attachment.
func InsertAttachment(attach *Attachment) (_ *Attachment, err error) {

if _, err := x.Insert(attach); err != nil {
return nil, err
}

return attach, nil
}

+ 10
- 2
modules/storage/local.go View File

@@ -65,6 +65,14 @@ func (l *LocalStorage) Delete(path string) error {
return os.Remove(p) return os.Remove(p)
} }


func (l *LocalStorage) PresignedGetURL(path string, fileName string) (string,error) {
return "",nil
func (l *LocalStorage) PresignedGetURL(path string, fileName string) (string, error) {
return "", nil
}

func (l *LocalStorage) PresignedPutURL(path string) (string, error) {
return "", nil
}

func (l *LocalStorage) HasObject(path string) (bool, error) {
return false, nil
} }

+ 42
- 7
modules/storage/minio.go View File

@@ -18,7 +18,10 @@ var (
_ ObjectStorage = &MinioStorage{} _ ObjectStorage = &MinioStorage{}
) )


const PRESIGNED_URL_EXPIRE_TIME = time.Hour * 24 * 7
const (
PresignedGetUrlExpireTime = time.Hour * 24 * 7
PresignedPutUrlExpireTime = time.Hour * 24 * 7
)


// MinioStorage returns a minio bucket storage // MinioStorage returns a minio bucket storage
type MinioStorage struct { type MinioStorage struct {
@@ -73,17 +76,49 @@ func (m *MinioStorage) Delete(path string) error {
return m.client.RemoveObject(m.bucket, m.buildMinioPath(path)) return m.client.RemoveObject(m.bucket, m.buildMinioPath(path))
} }


//Get Presigned URL
func (m *MinioStorage) PresignedGetURL(path string, fileName string) (string,error) {
//Get Presigned URL for get object
func (m *MinioStorage) PresignedGetURL(path string, fileName string) (string, error) {
// Set request parameters for content-disposition. // Set request parameters for content-disposition.
reqParams := make(url.Values) reqParams := make(url.Values)
reqParams.Set("response-content-disposition", "attachment; filename=\"" + fileName + "\"")
reqParams.Set("response-content-disposition", "attachment; filename=\""+fileName+"\"")

var preURL *url.URL
preURL, err := m.client.PresignedGetObject(m.bucket, m.buildMinioPath(path), PresignedGetUrlExpireTime, reqParams)
if err != nil {
return "", err
}

return preURL.String(), nil
}


//Get Presigned URL for put object
func (m *MinioStorage) PresignedPutURL(path string) (string, error) {
var preURL *url.URL var preURL *url.URL
preURL,err := m.client.PresignedGetObject(m.bucket, m.buildMinioPath(path), PRESIGNED_URL_EXPIRE_TIME, reqParams)
preURL, err := m.client.PresignedPutObject(m.bucket, m.buildMinioPath(path), PresignedPutUrlExpireTime)
if err != nil { if err != nil {
return "",err
return "", err
}

return preURL.String(), nil
}

//check if has the object
func (m *MinioStorage) HasObject(path string) (bool, error) {
hasObject := false
// Create a done channel to control 'ListObjects' go routine.
doneCh := make(chan struct{})

// Indicate to our routine to exit cleanly upon return.
defer close(doneCh)

objectCh := m.client.ListObjects(m.bucket, m.buildMinioPath(path), false, doneCh)
for object := range objectCh {
if object.Err != nil {
return hasObject, object.Err
}

hasObject = true
} }


return preURL.String(),nil
return hasObject, nil
} }

+ 2
- 0
modules/storage/storage.go View File

@@ -22,6 +22,8 @@ type ObjectStorage interface {
Open(path string) (io.ReadCloser, error) Open(path string) (io.ReadCloser, error)
Delete(path string) error Delete(path string) error
PresignedGetURL(path string, fileName string) (string, error) PresignedGetURL(path string, fileName string) (string, error)
PresignedPutURL(path string) (string, error)
HasObject(path string) (bool, error)
} }


// Copy copys a file from source ObjectStorage to dest ObjectStorage // Copy copys a file from source ObjectStorage to dest ObjectStorage


+ 4
- 0
modules/upload/filetype.go View File

@@ -31,6 +31,10 @@ func (err ErrFileTypeForbidden) Error() string {
func VerifyAllowedContentType(buf []byte, allowedTypes []string) error { func VerifyAllowedContentType(buf []byte, allowedTypes []string) error {
fileType := http.DetectContentType(buf) fileType := http.DetectContentType(buf)


return VerifyFileType(fileType, allowedTypes)
}

func VerifyFileType(fileType string, allowedTypes []string) error {
for _, t := range allowedTypes { for _, t := range allowedTypes {
t := strings.Trim(t, " ") t := strings.Trim(t, " ")




+ 65
- 0
routers/repo/attachment.go View File

@@ -16,6 +16,8 @@ import (
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/storage" "code.gitea.io/gitea/modules/storage"
"code.gitea.io/gitea/modules/upload" "code.gitea.io/gitea/modules/upload"

gouuid "github.com/satori/go.uuid"
) )


func RenderAttachmentSettings(ctx *context.Context) { func RenderAttachmentSettings(ctx *context.Context) {
@@ -210,3 +212,66 @@ func increaseDownloadCount(attach *models.Attachment, dataSet *models.Dataset) e


return nil return nil
} }

// Get a presigned url for put object
func GetPresignedPutObjectURL(ctx *context.Context) {
if !setting.Attachment.Enabled {
ctx.Error(404, "attachment is not enabled")
return
}

err := upload.VerifyFileType(ctx.Params("file_type"), strings.Split(setting.Attachment.AllowedTypes, ","))
if err != nil {
ctx.Error(400, err.Error())
return
}

if setting.Attachment.StoreType == storage.MinioStorageType {
uuid := gouuid.NewV4().String()
url, err := storage.Attachments.PresignedPutURL(models.AttachmentRelativePath(uuid))
if err != nil {
ctx.ServerError("PresignedPutURL", err)
return
}

ctx.JSON(200, map[string]string{
"uuid": uuid,
"url": url,
})
} else {
ctx.Error(404, "storage type is not enabled")
return
}
}

// AddAttachment response for add attachment record
func AddAttachment(ctx *context.Context) {
uuid := ctx.Query("uuid")
has, err := storage.Attachments.HasObject(models.AttachmentRelativePath(uuid))
if err != nil {
ctx.ServerError("HasObject", err)
return
}

if !has {
ctx.Error(404, "attachment has not been uploaded")
return
}

_, err = models.InsertAttachment(&models.Attachment{
UUID: uuid,
UploaderID: ctx.User.ID,
Name: ctx.Query("file_name"),
Size: ctx.QueryInt64("size"),
DatasetID: ctx.QueryInt64("dataset_id"),
})

if err != nil {
ctx.Error(500, fmt.Sprintf("InsertAttachment: %v", err))
return
}

ctx.JSON(200, map[string]string{
"result_code": "0",
})
}

+ 17
- 0
routers/repo/dataset.go View File

@@ -1,13 +1,18 @@
package repo package repo


import ( import (
"net/url"
"sort" "sort"


"code.gitea.io/gitea/modules/storage"

"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/auth" "code.gitea.io/gitea/modules/auth"
"code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"

gouuid "github.com/satori/go.uuid"
) )


const ( const (
@@ -77,6 +82,18 @@ func DatasetIndex(ctx *context.Context) {
ctx.Data["dataset"] = dataset ctx.Data["dataset"] = dataset
ctx.Data["Attachments"] = attachments ctx.Data["Attachments"] = attachments
ctx.Data["IsOwner"] = true ctx.Data["IsOwner"] = true
uuid := gouuid.NewV4().String()
tmpUrl, err := storage.Attachments.PresignedPutURL(models.AttachmentRelativePath(uuid))
if err != nil {
ctx.ServerError("PresignedPutURL", err)
}
preUrl, err := url.QueryUnescape(tmpUrl)
if err != nil {
ctx.ServerError("QueryUnescape", err)
}

ctx.Data["uuid"] = uuid
ctx.Data["url"] = preUrl
renderAttachmentSettings(ctx) renderAttachmentSettings(ctx)


ctx.HTML(200, tplIndex) ctx.HTML(200, tplIndex)


+ 2
- 0
routers/routes/routes.go View File

@@ -518,6 +518,8 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Group("/attachments", func() { m.Group("/attachments", func() {
m.Post("", repo.UploadAttachment) m.Post("", repo.UploadAttachment)
m.Post("/delete", repo.DeleteAttachment) m.Post("/delete", repo.DeleteAttachment)
m.Get("/get_pre_url", repo.GetPresignedPutObjectURL)
m.Post("/add", repo.AddAttachment)
m.Post("/private", repo.UpdatePublicAttachment) m.Post("/private", repo.UpdatePublicAttachment)
}, reqSignIn) }, reqSignIn)




+ 1
- 1
templates/repo/datasets/dataset.tmpl View File

@@ -2,7 +2,7 @@
<div class="field required dataset-files"> <div class="field required dataset-files">
<label>{{.i18n.Tr "dataset.file"}}</label> <label>{{.i18n.Tr "dataset.file"}}</label>
<div class="files"></div> <div class="files"></div>
<div class="ui dropzone" id="dataset" data-upload-url="{{AppSubUrl}}/attachments" data-accepts="{{.AttachmentAllowedTypes}}" data-remove-url="{{AppSubUrl}}/attachments/delete" data-csrf="{{.CsrfToken}}" dataset-id={{.dataset.ID}} data-max-file="100" data-dataset-id="{{.dataset.ID}}" data-max-size="{{.AttachmentMaxSize}}" data-default-message="{{.i18n.Tr "dropzone.default_message"}}" data-invalid-input-type="{{.i18n.Tr "dropzone.invalid_input_type"}}" data-file-too-big="{{.i18n.Tr "dropzone.file_too_big"}}" data-remove-file="{{.i18n.Tr "dropzone.remove_file"}}">
<div class="ui dropzone" id="dataset" data-upload-url="{{.url}}" data-uuid="{{.uuid}}" data-add-url="{{AppSubUrl}}/attachments/add" data-accepts="{{.AttachmentAllowedTypes}}" data-remove-url="{{AppSubUrl}}/attachments/delete" data-csrf="{{.CsrfToken}}" dataset-id={{.dataset.ID}} data-max-file="100" data-dataset-id="{{.dataset.ID}}" data-max-size="{{.AttachmentMaxSize}}" data-default-message="{{.i18n.Tr "dropzone.default_message"}}" data-invalid-input-type="{{.i18n.Tr "dropzone.invalid_input_type"}}" data-file-too-big="{{.i18n.Tr "dropzone.file_too_big"}}" data-remove-file="{{.i18n.Tr "dropzone.remove_file"}}">
</div> </div>
</div> </div>
</div> </div>

+ 14
- 7
web_src/js/index.js View File

@@ -2396,6 +2396,7 @@ $(document).ready(async () => {


await createDropzone('#dataset', { await createDropzone('#dataset', {
url: $dataset.data('upload-url'), url: $dataset.data('upload-url'),
method: 'put',
headers: {'X-Csrf-Token': csrf}, headers: {'X-Csrf-Token': csrf},
maxFiles: $dataset.data('max-file'), maxFiles: $dataset.data('max-file'),
maxFilesize: $dataset.data('max-size'), maxFilesize: $dataset.data('max-size'),
@@ -2411,13 +2412,19 @@ $(document).ready(async () => {
this.on('sending', (_file, _xhr, formData) => { this.on('sending', (_file, _xhr, formData) => {
formData.append('dataset_id', $dataset.data('dataset-id')); formData.append('dataset_id', $dataset.data('dataset-id'));
}); });
this.on('success', (file, data) => {
filenameDict[file.name] = data.uuid;
const input = $(`<input id="${data.uuid}" name="files" type="hidden">`).val(data.uuid);
$('.files').append(input);
});
this.on('queuecomplete', () => {
window.location.realod();
this.on('success', (file, _data) => {
const uuid = $dataset.data('uuid');
if ($dataset.data('add-url') && $dataset.data('csrf')) {
$.post($dataset.data('add-url'), {
uuid,
file_name: file.name,
size: file.size,
dataset_id: $dataset.data('dataset-id'),
_csrf: $dataset.data('csrf')
}).done(() => {
window.location.reload();
});
}
}); });
this.on('removedfile', (file) => { this.on('removedfile', (file) => {
if (file.name in filenameDict) { if (file.name in filenameDict) {


Loading…
Cancel
Save