2017-01-20 06:58:46 +00:00
// Copyright 2017 The Gitea Authors. All rights reserved.
2022-11-27 13:20:29 -05:00
// SPDX-License-Identifier: MIT
2017-01-20 06:58:46 +00:00
2021-11-19 21:39:57 +08:00
package repo
2017-01-20 06:58:46 +00:00
import (
2021-09-23 16:45:36 +01:00
"context"
2024-05-30 15:33:50 +08:00
"errors"
2017-01-20 06:58:46 +00:00
"fmt"
2021-11-16 18:18:25 +00:00
"net/url"
2024-05-30 15:33:50 +08:00
"os"
2017-01-20 06:58:46 +00:00
"path"
2021-09-19 19:49:59 +08:00
"code.gitea.io/gitea/models/db"
2024-05-30 15:33:50 +08:00
"code.gitea.io/gitea/modules/log"
2017-01-20 06:58:46 +00:00
"code.gitea.io/gitea/modules/setting"
2020-08-18 12:23:45 +08:00
"code.gitea.io/gitea/modules/storage"
2019-08-15 22:46:21 +08:00
"code.gitea.io/gitea/modules/timeutil"
2022-10-18 06:50:37 +01:00
"code.gitea.io/gitea/modules/util"
2017-01-20 06:58:46 +00:00
)
// Attachment represent a attachment of issue/comment/release.
type Attachment struct {
2023-04-12 11:05:23 +02:00
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
2024-03-12 15:23:44 +08:00
CommentID int64 ` xorm:"INDEX" `
2023-04-12 11:05:23 +02:00
Name string
DownloadCount int64 ` xorm:"DEFAULT 0" `
Size int64 ` xorm:"DEFAULT 0" `
CreatedUnix timeutil . TimeStamp ` xorm:"created" `
CustomDownloadURL string ` xorm:"-" `
2017-01-20 06:58:46 +00:00
}
2021-09-19 19:49:59 +08:00
func init ( ) {
db . RegisterModel ( new ( Attachment ) )
}
2017-04-20 10:31:31 +08:00
// IncreaseDownloadCount is update download count + 1
2023-09-15 08:13:19 +02:00
func ( a * Attachment ) IncreaseDownloadCount ( ctx context . Context ) error {
2017-04-20 10:31:31 +08:00
// Update download count.
2023-09-15 08:13:19 +02:00
if _ , err := db . GetEngine ( ctx ) . Exec ( "UPDATE `attachment` SET download_count=download_count+1 WHERE id=?" , a . ID ) ; err != nil {
2022-10-24 21:29:17 +02:00
return fmt . Errorf ( "increase attachment count: %w" , err )
2017-04-20 10:31:31 +08:00
}
return nil
}
2020-08-18 12:23:45 +08:00
// AttachmentRelativePath returns the relative path
func AttachmentRelativePath ( uuid string ) string {
return path . Join ( uuid [ 0 : 1 ] , uuid [ 1 : 2 ] , uuid )
2017-01-20 06:58:46 +00:00
}
2020-08-18 12:23:45 +08:00
// RelativePath returns the relative path of the attachment
func ( a * Attachment ) RelativePath ( ) string {
return AttachmentRelativePath ( a . UUID )
2017-01-20 06:58:46 +00:00
}
2018-03-06 02:22:16 +01:00
// DownloadURL returns the download url of the attached file
func ( a * Attachment ) DownloadURL ( ) string {
2023-04-12 11:05:23 +02:00
if a . CustomDownloadURL != "" {
return a . CustomDownloadURL
}
2021-11-16 18:18:25 +00:00
return setting . AppURL + "attachments/" + url . PathEscape ( a . UUID )
2018-03-06 02:22:16 +01:00
}
2021-11-19 21:39:57 +08:00
// 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 06:50:37 +01:00
func ( err ErrAttachmentNotExist ) Unwrap ( ) error {
return util . ErrNotExist
}
2022-05-20 22:08:52 +08:00
// GetAttachmentByID returns attachment by given id
func GetAttachmentByID ( ctx context . Context , id int64 ) ( * Attachment , error ) {
2020-06-18 01:50:11 +08:00
attach := & Attachment { }
2022-05-20 22:08:52 +08:00
if has , err := db . GetEngine ( ctx ) . ID ( id ) . Get ( attach ) ; err != nil {
2018-03-06 02:22:16 +01:00
return nil , err
} else if ! has {
return nil , ErrAttachmentNotExist { ID : id , UUID : "" }
}
return attach , nil
}
2022-05-20 22:08:52 +08:00
// GetAttachmentByUUID returns attachment by given UUID.
func GetAttachmentByUUID ( ctx context . Context , uuid string ) ( * Attachment , error ) {
2020-06-18 01:50:11 +08:00
attach := & Attachment { }
2022-05-20 22:08:52 +08:00
has , err := db . GetEngine ( ctx ) . Where ( "uuid=?" , uuid ) . Get ( attach )
2017-01-20 06:58:46 +00:00
if err != nil {
return nil , err
} else if ! has {
return nil , ErrAttachmentNotExist { 0 , uuid }
}
return attach , nil
}
2019-12-11 01:01:52 +01:00
// GetAttachmentsByUUIDs returns attachment by given UUID list.
2021-09-23 16:45:36 +01:00
func GetAttachmentsByUUIDs ( ctx context . Context , uuids [ ] string ) ( [ ] * Attachment , error ) {
2017-01-20 06:58:46 +00:00
if len ( uuids ) == 0 {
return [ ] * Attachment { } , nil
}
// Silently drop invalid uuids.
attachments := make ( [ ] * Attachment , 0 , len ( uuids ) )
2022-05-20 22:08:52 +08:00
return attachments , db . GetEngine ( ctx ) . In ( "uuid" , uuids ) . Find ( & attachments )
2017-01-20 06:58:46 +00:00
}
2022-11-15 08:08:59 +00:00
// 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 22:46:20 +08:00
}
2022-05-20 22:08:52 +08:00
// GetAttachmentsByIssueID returns all attachments of an issue.
func GetAttachmentsByIssueID ( ctx context . Context , issueID int64 ) ( [ ] * Attachment , error ) {
2017-01-20 06:58:46 +00:00
attachments := make ( [ ] * Attachment , 0 , 10 )
2021-11-19 21:39:57 +08:00
return attachments , db . GetEngine ( ctx ) . Where ( "issue_id = ? AND comment_id = 0" , issueID ) . Find ( & attachments )
2017-01-20 06:58:46 +00:00
}
2023-02-11 00:12:41 -08:00
// 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 06:58:46 +00:00
// GetAttachmentsByCommentID returns all attachments if comment by given ID.
2022-05-20 22:08:52 +08:00
func GetAttachmentsByCommentID ( ctx context . Context , commentID int64 ) ( [ ] * Attachment , error ) {
2017-01-20 06:58:46 +00:00
attachments := make ( [ ] * Attachment , 0 , 10 )
2021-11-19 21:39:57 +08:00
return attachments , db . GetEngine ( ctx ) . Where ( "comment_id=?" , commentID ) . Find ( & attachments )
2017-01-20 06:58:46 +00:00
}
2022-05-20 22:08:52 +08:00
// GetAttachmentByReleaseIDFileName returns attachment by given releaseId and fileName.
func GetAttachmentByReleaseIDFileName ( ctx context . Context , releaseID int64 , fileName string ) ( * Attachment , error ) {
2019-01-06 23:37:30 +01:00
attach := & Attachment { ReleaseID : releaseID , Name : fileName }
2022-05-20 22:08:52 +08:00
has , err := db . GetEngine ( ctx ) . Get ( attach )
2019-01-06 23:37:30 +01:00
if err != nil {
return nil , err
} else if ! has {
return nil , err
}
return attach , nil
}
2017-01-20 06:58:46 +00:00
// DeleteAttachment deletes the given attachment and optionally the associated file.
2023-09-15 08:13:19 +02:00
func DeleteAttachment ( ctx context . Context , a * Attachment , remove bool ) error {
_ , err := DeleteAttachments ( ctx , [ ] * Attachment { a } , remove )
2017-01-20 06:58:46 +00:00
return err
}
// DeleteAttachments deletes the given attachments and optionally the associated files.
2021-09-23 16:45:36 +01:00
func DeleteAttachments ( ctx context . Context , attachments [ ] * Attachment , remove bool ) ( int , error ) {
2017-12-24 15:04:22 -06:00
if len ( attachments ) == 0 {
return 0 , nil
}
2021-03-15 02:52:12 +08:00
ids := make ( [ ] int64 , 0 , len ( attachments ) )
2017-12-24 15:04:22 -06:00
for _ , a := range attachments {
ids = append ( ids , a . ID )
}
2021-09-23 16:45:36 +01:00
cnt , err := db . GetEngine ( ctx ) . In ( "id" , ids ) . NoAutoCondition ( ) . Delete ( attachments [ 0 ] )
2017-12-24 15:04:22 -06:00
if err != nil {
return 0 , err
}
if remove {
for i , a := range attachments {
2020-08-18 12:23:45 +08:00
if err := storage . Attachments . Delete ( a . RelativePath ( ) ) ; err != nil {
2024-05-30 15:33:50 +08:00
if ! errors . Is ( err , os . ErrNotExist ) {
return i , err
}
log . Warn ( "Attachment file not found when deleting: %s" , a . RelativePath ( ) )
2017-01-20 06:58:46 +00:00
}
}
}
2017-12-24 15:04:22 -06:00
return int ( cnt ) , nil
2017-01-20 06:58:46 +00:00
}
// DeleteAttachmentsByIssue deletes all attachments associated with the given issue.
2023-09-15 08:13:19 +02:00
func DeleteAttachmentsByIssue ( ctx context . Context , issueID int64 , remove bool ) ( int , error ) {
attachments , err := GetAttachmentsByIssueID ( ctx , issueID )
2017-01-20 06:58:46 +00:00
if err != nil {
return 0 , err
}
2023-09-15 08:13:19 +02:00
return DeleteAttachments ( ctx , attachments , remove )
2017-01-20 06:58:46 +00:00
}
// DeleteAttachmentsByComment deletes all attachments associated with the given comment.
2023-09-15 08:13:19 +02:00
func DeleteAttachmentsByComment ( ctx context . Context , commentID int64 , remove bool ) ( int , error ) {
attachments , err := GetAttachmentsByCommentID ( ctx , commentID )
2017-01-20 06:58:46 +00:00
if err != nil {
return 0 , err
}
2023-09-15 08:13:19 +02:00
return DeleteAttachments ( ctx , attachments , remove )
2017-01-20 06:58:46 +00:00
}
2018-03-06 02:22:16 +01:00
2021-03-23 00:09:51 +08:00
// UpdateAttachmentByUUID Updates attachment via uuid
2021-09-23 16:45:36 +01:00
func UpdateAttachmentByUUID ( ctx context . Context , attach * Attachment , cols ... string ) error {
2021-03-23 00:09:51 +08:00
if attach . UUID == "" {
2021-09-23 16:45:36 +01:00
return fmt . Errorf ( "attachment uuid should be not blank" )
2021-03-23 00:09:51 +08:00
}
2021-09-23 16:45:36 +01:00
_ , err := db . GetEngine ( ctx ) . Where ( "uuid=?" , attach . UUID ) . Cols ( cols ... ) . Update ( attach )
2021-03-23 00:09:51 +08:00
return err
}
2022-05-20 22:08:52 +08:00
// UpdateAttachment updates the given attachment in database
func UpdateAttachment ( ctx context . Context , atta * Attachment ) error {
2022-01-20 18:46:10 +01:00
sess := db . GetEngine ( ctx ) . Cols ( "name" , "issue_id" , "release_id" , "comment_id" , "download_count" )
2018-03-06 02:22:16 +01:00
if atta . ID != 0 && atta . UUID == "" {
2021-11-19 21:39:57 +08:00
sess = sess . ID ( atta . ID )
2018-03-06 02:22:16 +01:00
} else {
// Use uuid only if id is not set and uuid is set
2021-11-19 21:39:57 +08:00
sess = sess . Where ( "uuid = ?" , atta . UUID )
2018-03-06 02:22:16 +01:00
}
2021-11-19 21:39:57 +08:00
_ , err := sess . Update ( atta )
2018-03-06 02:22:16 +01:00
return err
}
2019-10-01 00:10:00 +08:00
// DeleteAttachmentsByRelease deletes all attachments associated with the given release.
2022-11-19 09:12:33 +01:00
func DeleteAttachmentsByRelease ( ctx context . Context , releaseID int64 ) error {
_ , err := db . GetEngine ( ctx ) . Where ( "release_id = ?" , releaseID ) . Delete ( & Attachment { } )
2019-10-01 00:10:00 +08:00
return err
}
2020-08-18 12:23:45 +08:00
2021-09-15 03:41:40 +08:00
// CountOrphanedAttachments returns the number of bad attachments
2022-11-19 09:12:33 +01:00
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 03:41:40 +08:00
Count ( new ( Attachment ) )
}
// DeleteOrphanedAttachments delete all bad attachments
2022-11-19 09:12:33 +01:00
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 03:41:40 +08:00
Delete ( new ( Attachment ) )
return err
}