2017-01-20 09:58:46 +03:00
// 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 (
2020-08-18 07:23:45 +03:00
"bytes"
2017-01-20 09:58:46 +03:00
"fmt"
"io"
"path"
"code.gitea.io/gitea/modules/setting"
2020-08-18 07:23:45 +03:00
"code.gitea.io/gitea/modules/storage"
2019-08-15 17:46:21 +03:00
"code.gitea.io/gitea/modules/timeutil"
2018-03-06 04:22:16 +03:00
2020-06-18 12:18:44 +03:00
gouuid "github.com/google/uuid"
2019-10-17 12:26:49 +03:00
"xorm.io/xorm"
2017-01-20 09:58:46 +03:00
)
// Attachment represent a attachment of issue/comment/release.
type Attachment struct {
2017-04-20 05:31:31 +03:00
ID int64 ` xorm:"pk autoincr" `
UUID string ` xorm:"uuid UNIQUE" `
IssueID int64 ` xorm:"INDEX" `
ReleaseID int64 ` xorm:"INDEX" `
2019-04-02 22:25:05 +03:00
UploaderID int64 ` xorm:"INDEX DEFAULT 0" ` // Notice: will be zero before this column added
2017-04-20 05:31:31 +03:00
CommentID int64
Name string
2019-08-15 17:46:21 +03:00
DownloadCount int64 ` xorm:"DEFAULT 0" `
Size int64 ` xorm:"DEFAULT 0" `
CreatedUnix timeutil . TimeStamp ` xorm:"created" `
2017-01-20 09:58:46 +03:00
}
2017-04-20 05:31:31 +03:00
// IncreaseDownloadCount is update download count + 1
func ( a * Attachment ) IncreaseDownloadCount ( ) error {
// Update download count.
2017-12-11 10:16:23 +03:00
if _ , err := x . Exec ( "UPDATE `attachment` SET download_count=download_count+1 WHERE id=?" , a . ID ) ; err != nil {
2017-04-20 05:31:31 +03:00
return fmt . Errorf ( "increase attachment count: %v" , err )
}
return nil
}
2020-08-18 07:23:45 +03:00
// AttachmentRelativePath returns the relative path
func AttachmentRelativePath ( uuid string ) string {
return path . Join ( uuid [ 0 : 1 ] , uuid [ 1 : 2 ] , uuid )
2017-01-20 09:58:46 +03:00
}
2020-08-18 07:23:45 +03:00
// RelativePath returns the relative path of the attachment
func ( a * Attachment ) RelativePath ( ) string {
return AttachmentRelativePath ( a . UUID )
2017-01-20 09:58:46 +03:00
}
2018-03-06 04:22:16 +03:00
// DownloadURL returns the download url of the attached file
func ( a * Attachment ) DownloadURL ( ) string {
return fmt . Sprintf ( "%sattachments/%s" , setting . AppURL , a . UUID )
}
2020-01-05 02:20:08 +03:00
// LinkedRepository returns the linked repo if any
func ( a * Attachment ) LinkedRepository ( ) ( * Repository , UnitType , error ) {
if a . IssueID != 0 {
iss , err := GetIssueByID ( a . IssueID )
if err != nil {
return nil , UnitTypeIssues , err
}
repo , err := GetRepositoryByID ( iss . RepoID )
2020-02-19 03:36:19 +03:00
unitType := UnitTypeIssues
if iss . IsPull {
unitType = UnitTypePullRequests
}
return repo , unitType , err
2020-01-05 02:20:08 +03:00
} else if a . ReleaseID != 0 {
rel , err := GetReleaseByID ( a . ReleaseID )
if err != nil {
return nil , UnitTypeReleases , err
}
repo , err := GetRepositoryByID ( rel . RepoID )
return repo , UnitTypeReleases , err
}
return nil , - 1 , nil
}
2017-01-20 09:58:46 +03:00
// NewAttachment creates a new attachment object.
2019-04-02 22:25:05 +03:00
func NewAttachment ( attach * Attachment , buf [ ] byte , file io . Reader ) ( _ * Attachment , err error ) {
2020-06-18 12:18:44 +03:00
attach . UUID = gouuid . New ( ) . String ( )
2017-01-20 09:58:46 +03:00
2020-08-18 07:23:45 +03:00
size , err := storage . Attachments . Save ( attach . RelativePath ( ) , io . MultiReader ( bytes . NewReader ( buf ) , file ) )
2017-01-20 09:58:46 +03:00
if err != nil {
return nil , fmt . Errorf ( "Create: %v" , err )
}
2020-08-18 07:23:45 +03:00
attach . Size = size
2018-03-31 04:10:44 +03:00
2017-01-20 09:58:46 +03:00
if _ , err := x . Insert ( attach ) ; err != nil {
return nil , err
}
return attach , nil
}
2018-03-06 04:22:16 +03:00
// GetAttachmentByID returns attachment by given id
func GetAttachmentByID ( id int64 ) ( * Attachment , error ) {
return getAttachmentByID ( x , id )
}
func getAttachmentByID ( e Engine , id int64 ) ( * Attachment , error ) {
2020-06-17 20:50:11 +03:00
attach := & Attachment { }
if has , err := e . ID ( id ) . Get ( attach ) ; err != nil {
2018-03-06 04:22:16 +03:00
return nil , err
} else if ! has {
return nil , ErrAttachmentNotExist { ID : id , UUID : "" }
}
return attach , nil
}
2017-01-20 09:58:46 +03:00
func getAttachmentByUUID ( e Engine , uuid string ) ( * Attachment , error ) {
2020-06-17 20:50:11 +03:00
attach := & Attachment { }
has , err := e . Where ( "uuid=?" , uuid ) . Get ( attach )
2017-01-20 09:58:46 +03:00
if err != nil {
return nil , err
} else if ! has {
return nil , ErrAttachmentNotExist { 0 , uuid }
}
return attach , nil
}
2019-12-11 03:01:52 +03:00
// GetAttachmentsByUUIDs returns attachment by given UUID list.
func GetAttachmentsByUUIDs ( uuids [ ] string ) ( [ ] * Attachment , error ) {
return getAttachmentsByUUIDs ( x , uuids )
}
2017-01-20 09:58:46 +03:00
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 )
}
2019-01-07 01:37:30 +03:00
// GetAttachmentByReleaseIDFileName returns attachment by given releaseId and fileName.
func GetAttachmentByReleaseIDFileName ( releaseID int64 , fileName string ) ( * Attachment , error ) {
return getAttachmentByReleaseIDFileName ( x , releaseID , fileName )
}
2017-01-20 09:58:46 +03:00
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 ) {
2017-10-01 19:52:35 +03:00
return getAttachmentsByCommentID ( x , commentID )
}
func getAttachmentsByCommentID ( e Engine , commentID int64 ) ( [ ] * Attachment , error ) {
2017-01-20 09:58:46 +03:00
attachments := make ( [ ] * Attachment , 0 , 10 )
2020-02-28 02:10:27 +03:00
return attachments , e . Where ( "comment_id=?" , commentID ) . Find ( & attachments )
2017-01-20 09:58:46 +03:00
}
2019-01-07 01:37:30 +03:00
// getAttachmentByReleaseIDFileName return a file based on the the following infos:
func getAttachmentByReleaseIDFileName ( e Engine , releaseID int64 , fileName string ) ( * Attachment , error ) {
attach := & Attachment { ReleaseID : releaseID , Name : fileName }
has , err := e . Get ( attach )
if err != nil {
return nil , err
} else if ! has {
return nil , err
}
return attach , nil
}
2017-01-20 09:58:46 +03:00
// 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 ) {
2017-12-25 00:04:22 +03:00
if len ( attachments ) == 0 {
return 0 , nil
}
var ids = make ( [ ] int64 , 0 , len ( attachments ) )
for _ , a := range attachments {
ids = append ( ids , a . ID )
}
cnt , err := x . In ( "id" , ids ) . NoAutoCondition ( ) . Delete ( attachments [ 0 ] )
if err != nil {
return 0 , err
}
if remove {
for i , a := range attachments {
2020-08-18 07:23:45 +03:00
if err := storage . Attachments . Delete ( a . RelativePath ( ) ) ; err != nil {
2017-01-20 09:58:46 +03:00
return i , err
}
}
}
2017-12-25 00:04:22 +03:00
return int ( cnt ) , nil
2017-01-20 09:58:46 +03:00
}
// 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 )
}
2018-03-06 04:22:16 +03:00
// UpdateAttachment updates the given attachment in database
func UpdateAttachment ( atta * Attachment ) error {
return updateAttachment ( x , atta )
}
func updateAttachment ( e Engine , atta * Attachment ) error {
var sess * xorm . Session
if atta . ID != 0 && atta . UUID == "" {
sess = e . ID ( atta . ID )
} else {
// Use uuid only if id is not set and uuid is set
sess = e . Where ( "uuid = ?" , atta . UUID )
}
_ , err := sess . Cols ( "name" , "issue_id" , "release_id" , "comment_id" , "download_count" ) . Update ( atta )
return err
}
2019-09-30 19:10:00 +03:00
// DeleteAttachmentsByRelease deletes all attachments associated with the given release.
func DeleteAttachmentsByRelease ( releaseID int64 ) error {
_ , err := x . Where ( "release_id = ?" , releaseID ) . Delete ( & Attachment { } )
return err
}
2020-08-18 07:23:45 +03:00
// IterateAttachment iterates attachments; it should not be used when Gitea is servicing users.
func IterateAttachment ( f func ( attach * Attachment ) error ) error {
var start int
const batchSize = 100
for {
var attachments = make ( [ ] * Attachment , 0 , batchSize )
if err := x . Limit ( batchSize , start ) . Find ( & attachments ) ; err != nil {
return err
}
if len ( attachments ) == 0 {
return nil
}
start += len ( attachments )
for _ , attach := range attachments {
if err := f ( attach ) ; err != nil {
return err
}
}
}
}