2017-01-20 12:28:46 +05:30
// Copyright 2017 The Gitea Authors. All rights reserved.
2022-11-27 23:50:29 +05:30
// SPDX-License-Identifier: MIT
2017-01-20 12:28:46 +05:30
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"
2022-10-18 11:20:37 +05:30
"code.gitea.io/gitea/modules/util"
2017-01-20 12:28:46 +05:30
)
// Attachment represent a attachment of issue/comment/release.
type Attachment struct {
2023-04-12 14:35:23 +05:30
ID int64 ` xorm:"pk autoincr" `
UUID string ` xorm:"uuid UNIQUE" `
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
UploaderID int64 ` xorm:"INDEX DEFAULT 0" ` // Notice: will be zero before this column added
CommentID int64
Name string
DownloadCount int64 ` xorm:"DEFAULT 0" `
Size int64 ` xorm:"DEFAULT 0" `
CreatedUnix timeutil . TimeStamp ` xorm:"created" `
CustomDownloadURL string ` xorm:"-" `
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
2023-09-15 11:43:19 +05:30
func ( a * Attachment ) IncreaseDownloadCount ( ctx context . Context ) error {
2017-04-20 08:01:31 +05:30
// Update download count.
2023-09-15 11:43:19 +05:30
if _ , err := db . GetEngine ( ctx ) . Exec ( "UPDATE `attachment` SET download_count=download_count+1 WHERE id=?" , a . ID ) ; err != nil {
2022-10-25 00:59:17 +05:30
return fmt . Errorf ( "increase attachment count: %w" , err )
2017-04-20 08:01:31 +05:30
}
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 {
2023-04-12 14:35:23 +05:30
if a . CustomDownloadURL != "" {
return a . CustomDownloadURL
}
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
// 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-10-18 11:20:37 +05:30
func ( err ErrAttachmentNotExist ) Unwrap ( ) error {
return util . ErrNotExist
}
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
}
2022-11-15 13:38:59 +05:30
// ExistAttachmentsByUUID returns true if attachment exists with the given UUID
func ExistAttachmentsByUUID ( ctx context . Context , uuid string ) ( bool , error ) {
return db . GetEngine ( ctx ) . 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
}
2023-02-11 13:42:41 +05:30
// GetAttachmentsByIssueIDImagesLatest returns the latest image attachments of an issue.
func GetAttachmentsByIssueIDImagesLatest ( ctx context . Context , issueID int64 ) ( [ ] * Attachment , error ) {
attachments := make ( [ ] * Attachment , 0 , 5 )
return attachments , db . GetEngine ( ctx ) . Where ( ` issue_id = ? AND ( name like ' % . apng '
OR name like ' % . avif '
OR name like ' % . bmp '
OR name like ' % . gif '
OR name like ' % . jpg '
OR name like ' % . jpeg '
OR name like ' % . jxl '
OR name like ' % . png '
OR name like ' % . svg '
OR name like ' % . webp ' ) ` , issueID ) . Desc ( "comment_id" ) . Limit ( 5 ) . 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.
2023-09-15 11:43:19 +05:30
func DeleteAttachment ( ctx context . Context , a * Attachment , remove bool ) error {
_ , err := DeleteAttachments ( ctx , [ ] * 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.
2023-09-15 11:43:19 +05:30
func DeleteAttachmentsByIssue ( ctx context . Context , issueID int64 , remove bool ) ( int , error ) {
attachments , err := GetAttachmentsByIssueID ( ctx , issueID )
2017-01-20 12:28:46 +05:30
if err != nil {
return 0 , err
}
2023-09-15 11:43:19 +05:30
return DeleteAttachments ( ctx , attachments , remove )
2017-01-20 12:28:46 +05:30
}
// DeleteAttachmentsByComment deletes all attachments associated with the given comment.
2023-09-15 11:43:19 +05:30
func DeleteAttachmentsByComment ( ctx context . Context , commentID int64 , remove bool ) ( int , error ) {
attachments , err := GetAttachmentsByCommentID ( ctx , commentID )
2017-01-20 12:28:46 +05:30
if err != nil {
return 0 , err
}
2023-09-15 11:43:19 +05:30
return DeleteAttachments ( ctx , 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.
2022-11-19 13:42:33 +05:30
func DeleteAttachmentsByRelease ( ctx context . Context , releaseID int64 ) error {
_ , err := db . GetEngine ( ctx ) . Where ( "release_id = ?" , releaseID ) . Delete ( & Attachment { } )
2019-09-30 21:40:00 +05:30
return err
}
2020-08-18 09:53:45 +05:30
2021-09-15 01:11:40 +05:30
// CountOrphanedAttachments returns the number of bad attachments
2022-11-19 13:42:33 +05:30
func CountOrphanedAttachments ( ctx context . Context ) ( int64 , error ) {
return db . GetEngine ( ctx ) . 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
2022-11-19 13:42:33 +05:30
func DeleteOrphanedAttachments ( ctx context . Context ) error {
_ , err := db . GetEngine ( ctx ) . 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
}