2017-01-20 12:28:46 +05:30
// 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.
2021-11-19 19:09:57 +05:30
package repo
2017-01-20 12:28:46 +05:30
import (
2021-09-23 21:15:36 +05:30
"context"
2017-01-20 12:28:46 +05:30
"fmt"
2021-11-16 23:48:25 +05:30
"net/url"
2017-01-20 12:28:46 +05:30
"path"
2021-09-19 17:19:59 +05:30
"code.gitea.io/gitea/models/db"
2017-01-20 12:28:46 +05:30
"code.gitea.io/gitea/modules/setting"
2020-08-18 09:53:45 +05:30
"code.gitea.io/gitea/modules/storage"
2019-08-15 20:16:21 +05:30
"code.gitea.io/gitea/modules/timeutil"
2017-01-20 12:28:46 +05:30
)
// Attachment represent a attachment of issue/comment/release.
type Attachment struct {
2017-04-20 08:01:31 +05:30
ID int64 ` xorm:"pk autoincr" `
UUID string ` xorm:"uuid UNIQUE" `
2021-09-08 20:49:30 +05:30
RepoID int64 ` xorm:"INDEX" ` // this should not be zero
IssueID int64 ` xorm:"INDEX" ` // maybe zero when creating
ReleaseID int64 ` xorm:"INDEX" ` // maybe zero when creating
2019-04-03 00:55:05 +05:30
UploaderID int64 ` xorm:"INDEX DEFAULT 0" ` // Notice: will be zero before this column added
2017-04-20 08:01:31 +05:30
CommentID int64
Name string
2019-08-15 20:16:21 +05:30
DownloadCount int64 ` xorm:"DEFAULT 0" `
Size int64 ` xorm:"DEFAULT 0" `
CreatedUnix timeutil . TimeStamp ` xorm:"created" `
2017-01-20 12:28:46 +05:30
}
2021-09-19 17:19:59 +05:30
func init ( ) {
db . RegisterModel ( new ( Attachment ) )
}
2017-04-20 08:01:31 +05:30
// IncreaseDownloadCount is update download count + 1
func ( a * Attachment ) IncreaseDownloadCount ( ) error {
// Update download count.
2021-09-23 21:15:36 +05:30
if _ , err := db . GetEngine ( db . DefaultContext ) . Exec ( "UPDATE `attachment` SET download_count=download_count+1 WHERE id=?" , a . ID ) ; err != nil {
2017-04-20 08:01:31 +05:30
return fmt . Errorf ( "increase attachment count: %v" , err )
}
return nil
}
2020-08-18 09:53:45 +05:30
// AttachmentRelativePath returns the relative path
func AttachmentRelativePath ( uuid string ) string {
return path . Join ( uuid [ 0 : 1 ] , uuid [ 1 : 2 ] , uuid )
2017-01-20 12:28:46 +05:30
}
2020-08-18 09:53:45 +05:30
// RelativePath returns the relative path of the attachment
func ( a * Attachment ) RelativePath ( ) string {
return AttachmentRelativePath ( a . UUID )
2017-01-20 12:28:46 +05:30
}
2018-03-06 06:52:16 +05:30
// DownloadURL returns the download url of the attached file
func ( a * Attachment ) DownloadURL ( ) string {
2021-11-16 23:48:25 +05:30
return setting . AppURL + "attachments/" + url . PathEscape ( a . UUID )
2018-03-06 06:52:16 +05:30
}
2021-11-19 19:09:57 +05:30
// _____ __ __ .__ __
// / _ \_/ |__/ |______ ____ | |__ _____ ____ _____/ |_
// / /_\ \ __\ __\__ \ _/ ___\| | \ / \_/ __ \ / \ __\
// / | \ | | | / __ \\ \___| Y \ Y Y \ ___/| | \ |
// \____|__ /__| |__| (____ /\___ >___| /__|_| /\___ >___| /__|
// \/ \/ \/ \/ \/ \/ \/
// ErrAttachmentNotExist represents a "AttachmentNotExist" kind of error.
type ErrAttachmentNotExist struct {
ID int64
UUID string
}
// IsErrAttachmentNotExist checks if an error is a ErrAttachmentNotExist.
func IsErrAttachmentNotExist ( err error ) bool {
_ , ok := err . ( ErrAttachmentNotExist )
return ok
}
func ( err ErrAttachmentNotExist ) Error ( ) string {
return fmt . Sprintf ( "attachment does not exist [id: %d, uuid: %s]" , err . ID , err . UUID )
}
2022-05-20 19:38:52 +05:30
// GetAttachmentByID returns attachment by given id
func GetAttachmentByID ( ctx context . Context , id int64 ) ( * Attachment , error ) {
2020-06-17 23:20:11 +05:30
attach := & Attachment { }
2022-05-20 19:38:52 +05:30
if has , err := db . GetEngine ( ctx ) . ID ( id ) . Get ( attach ) ; err != nil {
2018-03-06 06:52:16 +05:30
return nil , err
} else if ! has {
return nil , ErrAttachmentNotExist { ID : id , UUID : "" }
}
return attach , nil
}
2022-05-20 19:38:52 +05:30
// GetAttachmentByUUID returns attachment by given UUID.
func GetAttachmentByUUID ( ctx context . Context , uuid string ) ( * Attachment , error ) {
2020-06-17 23:20:11 +05:30
attach := & Attachment { }
2022-05-20 19:38:52 +05:30
has , err := db . GetEngine ( ctx ) . Where ( "uuid=?" , uuid ) . Get ( attach )
2017-01-20 12:28:46 +05:30
if err != nil {
return nil , err
} else if ! has {
return nil , ErrAttachmentNotExist { 0 , uuid }
}
return attach , nil
}
2019-12-11 05:31:52 +05:30
// GetAttachmentsByUUIDs returns attachment by given UUID list.
2021-09-23 21:15:36 +05:30
func GetAttachmentsByUUIDs ( ctx context . Context , uuids [ ] string ) ( [ ] * Attachment , error ) {
2017-01-20 12:28:46 +05:30
if len ( uuids ) == 0 {
return [ ] * Attachment { } , nil
}
// Silently drop invalid uuids.
attachments := make ( [ ] * Attachment , 0 , len ( uuids ) )
2022-05-20 19:38:52 +05:30
return attachments , db . GetEngine ( ctx ) . In ( "uuid" , uuids ) . Find ( & attachments )
2017-01-20 12:28:46 +05:30
}
2021-09-06 20:16:20 +05:30
// ExistAttachmentsByUUID returns true if attachment is exist by given UUID
func ExistAttachmentsByUUID ( uuid string ) ( bool , error ) {
2021-09-23 21:15:36 +05:30
return db . GetEngine ( db . DefaultContext ) . Where ( "`uuid`=?" , uuid ) . Exist ( new ( Attachment ) )
2021-09-06 20:16:20 +05:30
}
2022-05-20 19:38:52 +05:30
// GetAttachmentsByIssueID returns all attachments of an issue.
func GetAttachmentsByIssueID ( ctx context . Context , issueID int64 ) ( [ ] * Attachment , error ) {
2017-01-20 12:28:46 +05:30
attachments := make ( [ ] * Attachment , 0 , 10 )
2021-11-19 19:09:57 +05:30
return attachments , db . GetEngine ( ctx ) . Where ( "issue_id = ? AND comment_id = 0" , issueID ) . Find ( & attachments )
2017-01-20 12:28:46 +05:30
}
// GetAttachmentsByCommentID returns all attachments if comment by given ID.
2022-05-20 19:38:52 +05:30
func GetAttachmentsByCommentID ( ctx context . Context , commentID int64 ) ( [ ] * Attachment , error ) {
2017-01-20 12:28:46 +05:30
attachments := make ( [ ] * Attachment , 0 , 10 )
2021-11-19 19:09:57 +05:30
return attachments , db . GetEngine ( ctx ) . Where ( "comment_id=?" , commentID ) . Find ( & attachments )
2017-01-20 12:28:46 +05:30
}
2022-05-20 19:38:52 +05:30
// GetAttachmentByReleaseIDFileName returns attachment by given releaseId and fileName.
func GetAttachmentByReleaseIDFileName ( ctx context . Context , releaseID int64 , fileName string ) ( * Attachment , error ) {
2019-01-07 04:07:30 +05:30
attach := & Attachment { ReleaseID : releaseID , Name : fileName }
2022-05-20 19:38:52 +05:30
has , err := db . GetEngine ( ctx ) . Get ( attach )
2019-01-07 04:07:30 +05:30
if err != nil {
return nil , err
} else if ! has {
return nil , err
}
return attach , nil
}
2017-01-20 12:28:46 +05:30
// DeleteAttachment deletes the given attachment and optionally the associated file.
func DeleteAttachment ( a * Attachment , remove bool ) error {
2021-09-23 21:15:36 +05:30
_ , err := DeleteAttachments ( db . DefaultContext , [ ] * Attachment { a } , remove )
2017-01-20 12:28:46 +05:30
return err
}
// DeleteAttachments deletes the given attachments and optionally the associated files.
2021-09-23 21:15:36 +05:30
func DeleteAttachments ( ctx context . Context , attachments [ ] * Attachment , remove bool ) ( int , error ) {
2017-12-25 02:34:22 +05:30
if len ( attachments ) == 0 {
return 0 , nil
}
2021-03-15 00:22:12 +05:30
ids := make ( [ ] int64 , 0 , len ( attachments ) )
2017-12-25 02:34:22 +05:30
for _ , a := range attachments {
ids = append ( ids , a . ID )
}
2021-09-23 21:15:36 +05:30
cnt , err := db . GetEngine ( ctx ) . In ( "id" , ids ) . NoAutoCondition ( ) . Delete ( attachments [ 0 ] )
2017-12-25 02:34:22 +05:30
if err != nil {
return 0 , err
}
if remove {
for i , a := range attachments {
2020-08-18 09:53:45 +05:30
if err := storage . Attachments . Delete ( a . RelativePath ( ) ) ; err != nil {
2017-01-20 12:28:46 +05:30
return i , err
}
}
}
2017-12-25 02:34:22 +05:30
return int ( cnt ) , nil
2017-01-20 12:28:46 +05:30
}
// DeleteAttachmentsByIssue deletes all attachments associated with the given issue.
func DeleteAttachmentsByIssue ( issueID int64 , remove bool ) ( int , error ) {
2022-05-20 19:38:52 +05:30
attachments , err := GetAttachmentsByIssueID ( db . DefaultContext , issueID )
2017-01-20 12:28:46 +05:30
if err != nil {
return 0 , err
}
2021-09-23 21:15:36 +05:30
return DeleteAttachments ( db . DefaultContext , attachments , remove )
2017-01-20 12:28:46 +05:30
}
// DeleteAttachmentsByComment deletes all attachments associated with the given comment.
func DeleteAttachmentsByComment ( commentID int64 , remove bool ) ( int , error ) {
2022-05-20 19:38:52 +05:30
attachments , err := GetAttachmentsByCommentID ( db . DefaultContext , commentID )
2017-01-20 12:28:46 +05:30
if err != nil {
return 0 , err
}
2021-09-23 21:15:36 +05:30
return DeleteAttachments ( db . DefaultContext , attachments , remove )
2017-01-20 12:28:46 +05:30
}
2018-03-06 06:52:16 +05:30
2021-03-22 21:39:51 +05:30
// UpdateAttachmentByUUID Updates attachment via uuid
2021-09-23 21:15:36 +05:30
func UpdateAttachmentByUUID ( ctx context . Context , attach * Attachment , cols ... string ) error {
2021-03-22 21:39:51 +05:30
if attach . UUID == "" {
2021-09-23 21:15:36 +05:30
return fmt . Errorf ( "attachment uuid should be not blank" )
2021-03-22 21:39:51 +05:30
}
2021-09-23 21:15:36 +05:30
_ , err := db . GetEngine ( ctx ) . Where ( "uuid=?" , attach . UUID ) . Cols ( cols ... ) . Update ( attach )
2021-03-22 21:39:51 +05:30
return err
}
2022-05-20 19:38:52 +05:30
// UpdateAttachment updates the given attachment in database
func UpdateAttachment ( ctx context . Context , atta * Attachment ) error {
2022-01-20 23:16:10 +05:30
sess := db . GetEngine ( ctx ) . Cols ( "name" , "issue_id" , "release_id" , "comment_id" , "download_count" )
2018-03-06 06:52:16 +05:30
if atta . ID != 0 && atta . UUID == "" {
2021-11-19 19:09:57 +05:30
sess = sess . ID ( atta . ID )
2018-03-06 06:52:16 +05:30
} else {
// Use uuid only if id is not set and uuid is set
2021-11-19 19:09:57 +05:30
sess = sess . Where ( "uuid = ?" , atta . UUID )
2018-03-06 06:52:16 +05:30
}
2021-11-19 19:09:57 +05:30
_ , err := sess . Update ( atta )
2018-03-06 06:52:16 +05:30
return err
}
2019-09-30 21:40:00 +05:30
// DeleteAttachmentsByRelease deletes all attachments associated with the given release.
func DeleteAttachmentsByRelease ( releaseID int64 ) error {
2021-09-23 21:15:36 +05:30
_ , err := db . GetEngine ( db . DefaultContext ) . Where ( "release_id = ?" , releaseID ) . Delete ( & Attachment { } )
2019-09-30 21:40:00 +05:30
return err
}
2020-08-18 09:53:45 +05:30
// 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 {
2021-03-15 00:22:12 +05:30
attachments := make ( [ ] * Attachment , 0 , batchSize )
2021-09-23 21:15:36 +05:30
if err := db . GetEngine ( db . DefaultContext ) . Limit ( batchSize , start ) . Find ( & attachments ) ; err != nil {
2020-08-18 09:53:45 +05:30
return err
}
if len ( attachments ) == 0 {
return nil
}
start += len ( attachments )
for _ , attach := range attachments {
if err := f ( attach ) ; err != nil {
return err
}
}
}
}
2021-09-15 01:11:40 +05:30
// CountOrphanedAttachments returns the number of bad attachments
func CountOrphanedAttachments ( ) ( int64 , error ) {
2021-09-23 21:15:36 +05:30
return db . GetEngine ( db . DefaultContext ) . Where ( "(issue_id > 0 and issue_id not in (select id from issue)) or (release_id > 0 and release_id not in (select id from `release`))" ) .
2021-09-15 01:11:40 +05:30
Count ( new ( Attachment ) )
}
// DeleteOrphanedAttachments delete all bad attachments
func DeleteOrphanedAttachments ( ) error {
2021-09-23 21:15:36 +05:30
_ , err := db . GetEngine ( db . DefaultContext ) . Where ( "(issue_id > 0 and issue_id not in (select id from issue)) or (release_id > 0 and release_id not in (select id from `release`))" ) .
2021-09-15 01:11:40 +05:30
Delete ( new ( Attachment ) )
return err
}