2022-03-30 11:42:47 +03:00
// Copyright 2021 The Gitea Authors. All rights reserved.
2022-11-27 21:20:29 +03:00
// SPDX-License-Identifier: MIT
2022-03-30 11:42:47 +03:00
package packages
import (
"context"
"strconv"
"strings"
"time"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/timeutil"
2022-12-31 14:49:37 +03:00
"code.gitea.io/gitea/modules/util"
2022-03-30 11:42:47 +03:00
"xorm.io/builder"
)
func init ( ) {
db . RegisterModel ( new ( PackageFile ) )
}
var (
// ErrDuplicatePackageFile indicates a duplicated package file error
2022-12-31 14:49:37 +03:00
ErrDuplicatePackageFile = util . NewAlreadyExistErrorf ( "package file already exists" )
2022-03-30 11:42:47 +03:00
// ErrPackageFileNotExist indicates a package file not exist error
2022-12-31 14:49:37 +03:00
ErrPackageFileNotExist = util . NewNotExistErrorf ( "package file does not exist" )
2022-03-30 11:42:47 +03:00
)
// EmptyFileKey is a named constant for an empty file key
const EmptyFileKey = ""
// PackageFile represents a package file
type PackageFile struct {
ID int64 ` xorm:"pk autoincr" `
VersionID int64 ` xorm:"UNIQUE(s) INDEX NOT NULL" `
BlobID int64 ` xorm:"INDEX NOT NULL" `
Name string ` xorm:"NOT NULL" `
LowerName string ` xorm:"UNIQUE(s) INDEX NOT NULL" `
CompositeKey string ` xorm:"UNIQUE(s) INDEX" `
IsLead bool ` xorm:"NOT NULL DEFAULT false" `
CreatedUnix timeutil . TimeStamp ` xorm:"created INDEX NOT NULL" `
}
// TryInsertFile inserts a file. If the file exists already ErrDuplicatePackageFile is returned
func TryInsertFile ( ctx context . Context , pf * PackageFile ) ( * PackageFile , error ) {
e := db . GetEngine ( ctx )
2024-01-19 14:37:10 +03:00
existing := & PackageFile { }
2022-03-30 11:42:47 +03:00
2024-01-19 14:37:10 +03:00
has , err := e . Where ( builder . Eq {
"version_id" : pf . VersionID ,
"lower_name" : pf . LowerName ,
"composite_key" : pf . CompositeKey ,
} ) . Get ( existing )
2022-03-30 11:42:47 +03:00
if err != nil {
return nil , err
}
if has {
2024-01-19 14:37:10 +03:00
return existing , ErrDuplicatePackageFile
2022-03-30 11:42:47 +03:00
}
if _ , err = e . Insert ( pf ) ; err != nil {
return nil , err
}
return pf , nil
}
// GetFilesByVersionID gets all files of a version
func GetFilesByVersionID ( ctx context . Context , versionID int64 ) ( [ ] * PackageFile , error ) {
pfs := make ( [ ] * PackageFile , 0 , 10 )
return pfs , db . GetEngine ( ctx ) . Where ( "version_id = ?" , versionID ) . Find ( & pfs )
}
// GetFileForVersionByID gets a file of a version by id
func GetFileForVersionByID ( ctx context . Context , versionID , fileID int64 ) ( * PackageFile , error ) {
pf := & PackageFile {
VersionID : versionID ,
}
has , err := db . GetEngine ( ctx ) . ID ( fileID ) . Get ( pf )
if err != nil {
return nil , err
}
if ! has {
return nil , ErrPackageFileNotExist
}
return pf , nil
}
// GetFileForVersionByName gets a file of a version by name
func GetFileForVersionByName ( ctx context . Context , versionID int64 , name , key string ) ( * PackageFile , error ) {
if name == "" {
return nil , ErrPackageFileNotExist
}
2024-01-19 14:37:10 +03:00
pf := & PackageFile { }
2022-03-30 11:42:47 +03:00
2024-01-19 14:37:10 +03:00
has , err := db . GetEngine ( ctx ) . Where ( builder . Eq {
"version_id" : versionID ,
"lower_name" : strings . ToLower ( name ) ,
"composite_key" : key ,
} ) . Get ( pf )
2022-03-30 11:42:47 +03:00
if err != nil {
return nil , err
}
if ! has {
return nil , ErrPackageFileNotExist
}
return pf , nil
}
// DeleteFileByID deletes a file
func DeleteFileByID ( ctx context . Context , fileID int64 ) error {
_ , err := db . GetEngine ( ctx ) . ID ( fileID ) . Delete ( & PackageFile { } )
return err
}
// PackageFileSearchOptions are options for SearchXXX methods
type PackageFileSearchOptions struct {
2023-05-02 19:31:35 +03:00
OwnerID int64
2023-05-05 23:33:37 +03:00
PackageType Type
2023-05-02 19:31:35 +03:00
VersionID int64
Query string
CompositeKey string
Properties map [ string ] string
OlderThan time . Duration
HashAlgorithm string
Hash string
2022-03-30 11:42:47 +03:00
db . Paginator
}
func ( opts * PackageFileSearchOptions ) toConds ( ) builder . Cond {
cond := builder . NewCond ( )
if opts . VersionID != 0 {
cond = cond . And ( builder . Eq { "package_file.version_id" : opts . VersionID } )
} else if opts . OwnerID != 0 || ( opts . PackageType != "" && opts . PackageType != "all" ) {
var versionCond builder . Cond = builder . Eq {
"package_version.is_internal" : false ,
}
if opts . OwnerID != 0 {
versionCond = versionCond . And ( builder . Eq { "package.owner_id" : opts . OwnerID } )
}
if opts . PackageType != "" && opts . PackageType != "all" {
versionCond = versionCond . And ( builder . Eq { "package.type" : opts . PackageType } )
}
in := builder .
Select ( "package_version.id" ) .
From ( "package_version" ) .
2022-04-06 04:32:09 +03:00
InnerJoin ( "package" , "package.id = package_version.package_id" ) .
2022-03-30 11:42:47 +03:00
Where ( versionCond )
cond = cond . And ( builder . In ( "package_file.version_id" , in ) )
}
if opts . CompositeKey != "" {
cond = cond . And ( builder . Eq { "package_file.composite_key" : opts . CompositeKey } )
}
if opts . Query != "" {
cond = cond . And ( builder . Like { "package_file.lower_name" , strings . ToLower ( opts . Query ) } )
}
if len ( opts . Properties ) != 0 {
var propsCond builder . Cond = builder . Eq {
"package_property.ref_type" : PropertyTypeFile ,
}
propsCond = propsCond . And ( builder . Expr ( "package_property.ref_id = package_file.id" ) )
propsCondBlock := builder . NewCond ( )
for name , value := range opts . Properties {
propsCondBlock = propsCondBlock . Or ( builder . Eq {
"package_property.name" : name ,
"package_property.value" : value ,
} )
}
propsCond = propsCond . And ( propsCondBlock )
cond = cond . And ( builder . Eq {
strconv . Itoa ( len ( opts . Properties ) ) : builder . Select ( "COUNT(*)" ) . Where ( propsCond ) . From ( "package_property" ) ,
} )
}
if opts . OlderThan != 0 {
cond = cond . And ( builder . Lt { "package_file.created_unix" : time . Now ( ) . Add ( - opts . OlderThan ) . Unix ( ) } )
}
2023-05-02 19:31:35 +03:00
if opts . Hash != "" {
var field string
switch strings . ToLower ( opts . HashAlgorithm ) {
case "md5" :
field = "package_blob.hash_md5"
case "sha1" :
field = "package_blob.hash_sha1"
case "sha256" :
field = "package_blob.hash_sha256"
case "sha512" :
fallthrough
default : // default to SHA512 if not specified or unknown
field = "package_blob.hash_sha512"
}
innerCond := builder .
Expr ( "package_blob.id = package_file.blob_id" ) .
And ( builder . Eq { field : opts . Hash } )
cond = cond . And ( builder . Exists ( builder . Select ( "package_blob.id" ) . From ( "package_blob" ) . Where ( innerCond ) ) )
}
2022-03-30 11:42:47 +03:00
return cond
}
// SearchFiles gets all files of packages matching the search options
func SearchFiles ( ctx context . Context , opts * PackageFileSearchOptions ) ( [ ] * PackageFile , int64 , error ) {
sess := db . GetEngine ( ctx ) .
Where ( opts . toConds ( ) )
if opts . Paginator != nil {
sess = db . SetSessionPagination ( sess , opts )
}
pfs := make ( [ ] * PackageFile , 0 , 10 )
count , err := sess . FindAndCount ( & pfs )
return pfs , count , err
}
2022-11-09 09:34:27 +03:00
2023-01-18 18:52:04 +03:00
// CalculateFileSize sums up all blob sizes matching the search options.
2022-11-09 09:34:27 +03:00
// It does NOT respect the deduplication of blobs.
2023-01-18 18:52:04 +03:00
func CalculateFileSize ( ctx context . Context , opts * PackageFileSearchOptions ) ( int64 , error ) {
2022-11-09 09:34:27 +03:00
return db . GetEngine ( ctx ) .
Table ( "package_file" ) .
Where ( opts . toConds ( ) ) .
Join ( "INNER" , "package_blob" , "package_blob.id = package_file.blob_id" ) .
SumInt ( new ( PackageBlob ) , "size" )
}