Moved attachments into seperate go filemaster
@@ -0,0 +1,176 @@ | |||||
// Copyright 2017 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 models | |||||
import ( | |||||
"fmt" | |||||
"io" | |||||
"mime/multipart" | |||||
"os" | |||||
"path" | |||||
"time" | |||||
"github.com/go-xorm/xorm" | |||||
gouuid "github.com/satori/go.uuid" | |||||
"code.gitea.io/gitea/modules/setting" | |||||
) | |||||
// Attachment represent a attachment of issue/comment/release. | |||||
type Attachment struct { | |||||
ID int64 `xorm:"pk autoincr"` | |||||
UUID string `xorm:"uuid UNIQUE"` | |||||
IssueID int64 `xorm:"INDEX"` | |||||
CommentID int64 | |||||
ReleaseID int64 `xorm:"INDEX"` | |||||
Name string | |||||
Created time.Time `xorm:"-"` | |||||
CreatedUnix int64 | |||||
} | |||||
// BeforeInsert is invoked from XORM before inserting an object of this type. | |||||
func (a *Attachment) BeforeInsert() { | |||||
a.CreatedUnix = time.Now().Unix() | |||||
} | |||||
// AfterSet is invoked from XORM after setting the value of a field of | |||||
// this object. | |||||
func (a *Attachment) AfterSet(colName string, _ xorm.Cell) { | |||||
switch colName { | |||||
case "created_unix": | |||||
a.Created = time.Unix(a.CreatedUnix, 0).Local() | |||||
} | |||||
} | |||||
// AttachmentLocalPath returns where attachment is stored in local file | |||||
// system based on given UUID. | |||||
func AttachmentLocalPath(uuid string) string { | |||||
return path.Join(setting.AttachmentPath, uuid[0:1], uuid[1:2], uuid) | |||||
} | |||||
// LocalPath returns where attachment is stored in local file system. | |||||
func (a *Attachment) LocalPath() string { | |||||
return AttachmentLocalPath(a.UUID) | |||||
} | |||||
// NewAttachment creates a new attachment object. | |||||
func NewAttachment(name string, buf []byte, file multipart.File) (_ *Attachment, err error) { | |||||
attach := &Attachment{ | |||||
UUID: gouuid.NewV4().String(), | |||||
Name: name, | |||||
} | |||||
localPath := attach.LocalPath() | |||||
if err = os.MkdirAll(path.Dir(localPath), os.ModePerm); err != nil { | |||||
return nil, fmt.Errorf("MkdirAll: %v", err) | |||||
} | |||||
fw, err := os.Create(localPath) | |||||
if err != nil { | |||||
return nil, fmt.Errorf("Create: %v", err) | |||||
} | |||||
defer fw.Close() | |||||
if _, err = fw.Write(buf); err != nil { | |||||
return nil, fmt.Errorf("Write: %v", err) | |||||
} else if _, err = io.Copy(fw, file); err != nil { | |||||
return nil, fmt.Errorf("Copy: %v", err) | |||||
} | |||||
if _, err := x.Insert(attach); err != nil { | |||||
return nil, err | |||||
} | |||||
return attach, nil | |||||
} | |||||
func getAttachmentByUUID(e Engine, uuid string) (*Attachment, error) { | |||||
attach := &Attachment{UUID: uuid} | |||||
has, err := x.Get(attach) | |||||
if err != nil { | |||||
return nil, err | |||||
} else if !has { | |||||
return nil, ErrAttachmentNotExist{0, uuid} | |||||
} | |||||
return attach, nil | |||||
} | |||||
func getAttachmentsByUUIDs(e Engine, uuids []string) ([]*Attachment, error) { | |||||
if len(uuids) == 0 { | |||||
return []*Attachment{}, nil | |||||
} | |||||
// Silently drop invalid uuids. | |||||
attachments := make([]*Attachment, 0, len(uuids)) | |||||
return attachments, e.In("uuid", uuids).Find(&attachments) | |||||
} | |||||
// GetAttachmentByUUID returns attachment by given UUID. | |||||
func GetAttachmentByUUID(uuid string) (*Attachment, error) { | |||||
return getAttachmentByUUID(x, uuid) | |||||
} | |||||
func getAttachmentsByIssueID(e Engine, issueID int64) ([]*Attachment, error) { | |||||
attachments := make([]*Attachment, 0, 10) | |||||
return attachments, e.Where("issue_id = ? AND comment_id = 0", issueID).Find(&attachments) | |||||
} | |||||
// GetAttachmentsByIssueID returns all attachments of an issue. | |||||
func GetAttachmentsByIssueID(issueID int64) ([]*Attachment, error) { | |||||
return getAttachmentsByIssueID(x, issueID) | |||||
} | |||||
// GetAttachmentsByCommentID returns all attachments if comment by given ID. | |||||
func GetAttachmentsByCommentID(commentID int64) ([]*Attachment, error) { | |||||
attachments := make([]*Attachment, 0, 10) | |||||
return attachments, x.Where("comment_id=?", commentID).Find(&attachments) | |||||
} | |||||
// DeleteAttachment deletes the given attachment and optionally the associated file. | |||||
func DeleteAttachment(a *Attachment, remove bool) error { | |||||
_, err := DeleteAttachments([]*Attachment{a}, remove) | |||||
return err | |||||
} | |||||
// DeleteAttachments deletes the given attachments and optionally the associated files. | |||||
func DeleteAttachments(attachments []*Attachment, remove bool) (int, error) { | |||||
for i, a := range attachments { | |||||
if remove { | |||||
if err := os.Remove(a.LocalPath()); err != nil { | |||||
return i, err | |||||
} | |||||
} | |||||
if _, err := x.Delete(a); err != nil { | |||||
return i, err | |||||
} | |||||
} | |||||
return len(attachments), nil | |||||
} | |||||
// DeleteAttachmentsByIssue deletes all attachments associated with the given issue. | |||||
func DeleteAttachmentsByIssue(issueID int64, remove bool) (int, error) { | |||||
attachments, err := GetAttachmentsByIssueID(issueID) | |||||
if err != nil { | |||||
return 0, err | |||||
} | |||||
return DeleteAttachments(attachments, remove) | |||||
} | |||||
// DeleteAttachmentsByComment deletes all attachments associated with the given comment. | |||||
func DeleteAttachmentsByComment(commentID int64, remove bool) (int, error) { | |||||
attachments, err := GetAttachmentsByCommentID(commentID) | |||||
if err != nil { | |||||
return 0, err | |||||
} | |||||
return DeleteAttachments(attachments, remove) | |||||
} |
@@ -7,17 +7,12 @@ package models | |||||
import ( | import ( | ||||
"errors" | "errors" | ||||
"fmt" | "fmt" | ||||
"io" | |||||
"mime/multipart" | |||||
"os" | |||||
"path" | |||||
"strings" | "strings" | ||||
"time" | "time" | ||||
api "code.gitea.io/sdk/gitea" | api "code.gitea.io/sdk/gitea" | ||||
"github.com/Unknwon/com" | "github.com/Unknwon/com" | ||||
"github.com/go-xorm/xorm" | "github.com/go-xorm/xorm" | ||||
gouuid "github.com/satori/go.uuid" | |||||
"code.gitea.io/gitea/modules/base" | "code.gitea.io/gitea/modules/base" | ||||
"code.gitea.io/gitea/modules/log" | "code.gitea.io/gitea/modules/log" | ||||
@@ -1740,158 +1735,3 @@ func DeleteMilestoneByRepoID(repoID, id int64) error { | |||||
return sess.Commit() | return sess.Commit() | ||||
} | } | ||||
// Attachment represent a attachment of issue/comment/release. | |||||
type Attachment struct { | |||||
ID int64 `xorm:"pk autoincr"` | |||||
UUID string `xorm:"uuid UNIQUE"` | |||||
IssueID int64 `xorm:"INDEX"` | |||||
CommentID int64 | |||||
ReleaseID int64 `xorm:"INDEX"` | |||||
Name string | |||||
Created time.Time `xorm:"-"` | |||||
CreatedUnix int64 | |||||
} | |||||
// BeforeInsert is invoked from XORM before inserting an object of this type. | |||||
func (a *Attachment) BeforeInsert() { | |||||
a.CreatedUnix = time.Now().Unix() | |||||
} | |||||
// AfterSet is invoked from XORM after setting the value of a field of | |||||
// this object. | |||||
func (a *Attachment) AfterSet(colName string, _ xorm.Cell) { | |||||
switch colName { | |||||
case "created_unix": | |||||
a.Created = time.Unix(a.CreatedUnix, 0).Local() | |||||
} | |||||
} | |||||
// AttachmentLocalPath returns where attachment is stored in local file | |||||
// system based on given UUID. | |||||
func AttachmentLocalPath(uuid string) string { | |||||
return path.Join(setting.AttachmentPath, uuid[0:1], uuid[1:2], uuid) | |||||
} | |||||
// LocalPath returns where attachment is stored in local file system. | |||||
func (a *Attachment) LocalPath() string { | |||||
return AttachmentLocalPath(a.UUID) | |||||
} | |||||
// NewAttachment creates a new attachment object. | |||||
func NewAttachment(name string, buf []byte, file multipart.File) (_ *Attachment, err error) { | |||||
attach := &Attachment{ | |||||
UUID: gouuid.NewV4().String(), | |||||
Name: name, | |||||
} | |||||
localPath := attach.LocalPath() | |||||
if err = os.MkdirAll(path.Dir(localPath), os.ModePerm); err != nil { | |||||
return nil, fmt.Errorf("MkdirAll: %v", err) | |||||
} | |||||
fw, err := os.Create(localPath) | |||||
if err != nil { | |||||
return nil, fmt.Errorf("Create: %v", err) | |||||
} | |||||
defer fw.Close() | |||||
if _, err = fw.Write(buf); err != nil { | |||||
return nil, fmt.Errorf("Write: %v", err) | |||||
} else if _, err = io.Copy(fw, file); err != nil { | |||||
return nil, fmt.Errorf("Copy: %v", err) | |||||
} | |||||
if _, err := x.Insert(attach); err != nil { | |||||
return nil, err | |||||
} | |||||
return attach, nil | |||||
} | |||||
func getAttachmentByUUID(e Engine, uuid string) (*Attachment, error) { | |||||
attach := &Attachment{UUID: uuid} | |||||
has, err := x.Get(attach) | |||||
if err != nil { | |||||
return nil, err | |||||
} else if !has { | |||||
return nil, ErrAttachmentNotExist{0, uuid} | |||||
} | |||||
return attach, nil | |||||
} | |||||
func getAttachmentsByUUIDs(e Engine, uuids []string) ([]*Attachment, error) { | |||||
if len(uuids) == 0 { | |||||
return []*Attachment{}, nil | |||||
} | |||||
// Silently drop invalid uuids. | |||||
attachments := make([]*Attachment, 0, len(uuids)) | |||||
return attachments, e.In("uuid", uuids).Find(&attachments) | |||||
} | |||||
// GetAttachmentByUUID returns attachment by given UUID. | |||||
func GetAttachmentByUUID(uuid string) (*Attachment, error) { | |||||
return getAttachmentByUUID(x, uuid) | |||||
} | |||||
func getAttachmentsByIssueID(e Engine, issueID int64) ([]*Attachment, error) { | |||||
attachments := make([]*Attachment, 0, 10) | |||||
return attachments, e.Where("issue_id = ? AND comment_id = 0", issueID).Find(&attachments) | |||||
} | |||||
// GetAttachmentsByIssueID returns all attachments of an issue. | |||||
func GetAttachmentsByIssueID(issueID int64) ([]*Attachment, error) { | |||||
return getAttachmentsByIssueID(x, issueID) | |||||
} | |||||
// GetAttachmentsByCommentID returns all attachments if comment by given ID. | |||||
func GetAttachmentsByCommentID(commentID int64) ([]*Attachment, error) { | |||||
attachments := make([]*Attachment, 0, 10) | |||||
return attachments, x.Where("comment_id=?", commentID).Find(&attachments) | |||||
} | |||||
// DeleteAttachment deletes the given attachment and optionally the associated file. | |||||
func DeleteAttachment(a *Attachment, remove bool) error { | |||||
_, err := DeleteAttachments([]*Attachment{a}, remove) | |||||
return err | |||||
} | |||||
// DeleteAttachments deletes the given attachments and optionally the associated files. | |||||
func DeleteAttachments(attachments []*Attachment, remove bool) (int, error) { | |||||
for i, a := range attachments { | |||||
if remove { | |||||
if err := os.Remove(a.LocalPath()); err != nil { | |||||
return i, err | |||||
} | |||||
} | |||||
if _, err := x.Delete(a); err != nil { | |||||
return i, err | |||||
} | |||||
} | |||||
return len(attachments), nil | |||||
} | |||||
// DeleteAttachmentsByIssue deletes all attachments associated with the given issue. | |||||
func DeleteAttachmentsByIssue(issueID int64, remove bool) (int, error) { | |||||
attachments, err := GetAttachmentsByIssueID(issueID) | |||||
if err != nil { | |||||
return 0, err | |||||
} | |||||
return DeleteAttachments(attachments, remove) | |||||
} | |||||
// DeleteAttachmentsByComment deletes all attachments associated with the given comment. | |||||
func DeleteAttachmentsByComment(commentID int64, remove bool) (int, error) { | |||||
attachments, err := GetAttachmentsByCommentID(commentID) | |||||
if err != nil { | |||||
return 0, err | |||||
} | |||||
return DeleteAttachments(attachments, remove) | |||||
} |
@@ -0,0 +1,73 @@ | |||||
// Copyright 2017 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 repo | |||||
import ( | |||||
"fmt" | |||||
"net/http" | |||||
"strings" | |||||
"code.gitea.io/gitea/models" | |||||
"code.gitea.io/gitea/modules/context" | |||||
"code.gitea.io/gitea/modules/log" | |||||
"code.gitea.io/gitea/modules/setting" | |||||
) | |||||
func renderAttachmentSettings(ctx *context.Context) { | |||||
ctx.Data["RequireDropzone"] = true | |||||
ctx.Data["IsAttachmentEnabled"] = setting.AttachmentEnabled | |||||
ctx.Data["AttachmentAllowedTypes"] = setting.AttachmentAllowedTypes | |||||
ctx.Data["AttachmentMaxSize"] = setting.AttachmentMaxSize | |||||
ctx.Data["AttachmentMaxFiles"] = setting.AttachmentMaxFiles | |||||
} | |||||
// UploadAttachment response for uploading issue's attachment | |||||
func UploadAttachment(ctx *context.Context) { | |||||
if !setting.AttachmentEnabled { | |||||
ctx.Error(404, "attachment is not enabled") | |||||
return | |||||
} | |||||
file, header, err := ctx.Req.FormFile("file") | |||||
if err != nil { | |||||
ctx.Error(500, fmt.Sprintf("FormFile: %v", err)) | |||||
return | |||||
} | |||||
defer file.Close() | |||||
buf := make([]byte, 1024) | |||||
n, _ := file.Read(buf) | |||||
if n > 0 { | |||||
buf = buf[:n] | |||||
} | |||||
fileType := http.DetectContentType(buf) | |||||
allowedTypes := strings.Split(setting.AttachmentAllowedTypes, ",") | |||||
allowed := false | |||||
for _, t := range allowedTypes { | |||||
t := strings.Trim(t, " ") | |||||
if t == "*/*" || t == fileType { | |||||
allowed = true | |||||
break | |||||
} | |||||
} | |||||
if !allowed { | |||||
ctx.Error(400, ErrFileTypeForbidden.Error()) | |||||
return | |||||
} | |||||
attach, err := models.NewAttachment(header.Filename, buf, file) | |||||
if err != nil { | |||||
ctx.Error(500, fmt.Sprintf("NewAttachment: %v", err)) | |||||
return | |||||
} | |||||
log.Trace("New attachment uploaded: %s", attach.UUID) | |||||
ctx.JSON(200, map[string]string{ | |||||
"uuid": attach.UUID, | |||||
}) | |||||
} | |||||
@@ -9,7 +9,6 @@ import ( | |||||
"fmt" | "fmt" | ||||
"io" | "io" | ||||
"io/ioutil" | "io/ioutil" | ||||
"net/http" | |||||
"net/url" | "net/url" | ||||
"strings" | "strings" | ||||
"time" | "time" | ||||
@@ -268,14 +267,6 @@ func Issues(ctx *context.Context) { | |||||
ctx.HTML(200, tplIssues) | ctx.HTML(200, tplIssues) | ||||
} | } | ||||
func renderAttachmentSettings(ctx *context.Context) { | |||||
ctx.Data["RequireDropzone"] = true | |||||
ctx.Data["IsAttachmentEnabled"] = setting.AttachmentEnabled | |||||
ctx.Data["AttachmentAllowedTypes"] = setting.AttachmentAllowedTypes | |||||
ctx.Data["AttachmentMaxSize"] = setting.AttachmentMaxSize | |||||
ctx.Data["AttachmentMaxFiles"] = setting.AttachmentMaxFiles | |||||
} | |||||
// RetrieveRepoMilestonesAndAssignees find all the milestones and assignees of a repository | // RetrieveRepoMilestonesAndAssignees find all the milestones and assignees of a repository | ||||
func RetrieveRepoMilestonesAndAssignees(ctx *context.Context, repo *models.Repository) { | func RetrieveRepoMilestonesAndAssignees(ctx *context.Context, repo *models.Repository) { | ||||
var err error | var err error | ||||
@@ -477,54 +468,6 @@ func NewIssuePost(ctx *context.Context, form auth.CreateIssueForm) { | |||||
ctx.Redirect(ctx.Repo.RepoLink + "/issues/" + com.ToStr(issue.Index)) | ctx.Redirect(ctx.Repo.RepoLink + "/issues/" + com.ToStr(issue.Index)) | ||||
} | } | ||||
// UploadAttachment response for uploading issue's attachment | |||||
func UploadAttachment(ctx *context.Context) { | |||||
if !setting.AttachmentEnabled { | |||||
ctx.Error(404, "attachment is not enabled") | |||||
return | |||||
} | |||||
file, header, err := ctx.Req.FormFile("file") | |||||
if err != nil { | |||||
ctx.Error(500, fmt.Sprintf("FormFile: %v", err)) | |||||
return | |||||
} | |||||
defer file.Close() | |||||
buf := make([]byte, 1024) | |||||
n, _ := file.Read(buf) | |||||
if n > 0 { | |||||
buf = buf[:n] | |||||
} | |||||
fileType := http.DetectContentType(buf) | |||||
allowedTypes := strings.Split(setting.AttachmentAllowedTypes, ",") | |||||
allowed := false | |||||
for _, t := range allowedTypes { | |||||
t := strings.Trim(t, " ") | |||||
if t == "*/*" || t == fileType { | |||||
allowed = true | |||||
break | |||||
} | |||||
} | |||||
if !allowed { | |||||
ctx.Error(400, ErrFileTypeForbidden.Error()) | |||||
return | |||||
} | |||||
attach, err := models.NewAttachment(header.Filename, buf, file) | |||||
if err != nil { | |||||
ctx.Error(500, fmt.Sprintf("NewAttachment: %v", err)) | |||||
return | |||||
} | |||||
log.Trace("New attachment uploaded: %s", attach.UUID) | |||||
ctx.JSON(200, map[string]string{ | |||||
"uuid": attach.UUID, | |||||
}) | |||||
} | |||||
// ViewIssue render issue view page | // ViewIssue render issue view page | ||||
func ViewIssue(ctx *context.Context) { | func ViewIssue(ctx *context.Context) { | ||||
ctx.Data["RequireHighlightJS"] = true | ctx.Data["RequireHighlightJS"] = true | ||||