code.gitea.io/gitea@v1.21.7/models/packages/package_file.go (about) 1 // Copyright 2021 The Gitea Authors. All rights reserved. 2 // SPDX-License-Identifier: MIT 3 4 package packages 5 6 import ( 7 "context" 8 "strconv" 9 "strings" 10 "time" 11 12 "code.gitea.io/gitea/models/db" 13 "code.gitea.io/gitea/modules/timeutil" 14 "code.gitea.io/gitea/modules/util" 15 16 "xorm.io/builder" 17 ) 18 19 func init() { 20 db.RegisterModel(new(PackageFile)) 21 } 22 23 var ( 24 // ErrDuplicatePackageFile indicates a duplicated package file error 25 ErrDuplicatePackageFile = util.NewAlreadyExistErrorf("package file already exists") 26 // ErrPackageFileNotExist indicates a package file not exist error 27 ErrPackageFileNotExist = util.NewNotExistErrorf("package file does not exist") 28 ) 29 30 // EmptyFileKey is a named constant for an empty file key 31 const EmptyFileKey = "" 32 33 // PackageFile represents a package file 34 type PackageFile struct { 35 ID int64 `xorm:"pk autoincr"` 36 VersionID int64 `xorm:"UNIQUE(s) INDEX NOT NULL"` 37 BlobID int64 `xorm:"INDEX NOT NULL"` 38 Name string `xorm:"NOT NULL"` 39 LowerName string `xorm:"UNIQUE(s) INDEX NOT NULL"` 40 CompositeKey string `xorm:"UNIQUE(s) INDEX"` 41 IsLead bool `xorm:"NOT NULL DEFAULT false"` 42 CreatedUnix timeutil.TimeStamp `xorm:"created INDEX NOT NULL"` 43 } 44 45 // TryInsertFile inserts a file. If the file exists already ErrDuplicatePackageFile is returned 46 func TryInsertFile(ctx context.Context, pf *PackageFile) (*PackageFile, error) { 47 e := db.GetEngine(ctx) 48 49 key := &PackageFile{ 50 VersionID: pf.VersionID, 51 LowerName: pf.LowerName, 52 CompositeKey: pf.CompositeKey, 53 } 54 55 has, err := e.Get(key) 56 if err != nil { 57 return nil, err 58 } 59 if has { 60 return pf, ErrDuplicatePackageFile 61 } 62 if _, err = e.Insert(pf); err != nil { 63 return nil, err 64 } 65 return pf, nil 66 } 67 68 // GetFilesByVersionID gets all files of a version 69 func GetFilesByVersionID(ctx context.Context, versionID int64) ([]*PackageFile, error) { 70 pfs := make([]*PackageFile, 0, 10) 71 return pfs, db.GetEngine(ctx).Where("version_id = ?", versionID).Find(&pfs) 72 } 73 74 // GetFileForVersionByID gets a file of a version by id 75 func GetFileForVersionByID(ctx context.Context, versionID, fileID int64) (*PackageFile, error) { 76 pf := &PackageFile{ 77 VersionID: versionID, 78 } 79 80 has, err := db.GetEngine(ctx).ID(fileID).Get(pf) 81 if err != nil { 82 return nil, err 83 } 84 if !has { 85 return nil, ErrPackageFileNotExist 86 } 87 return pf, nil 88 } 89 90 // GetFileForVersionByName gets a file of a version by name 91 func GetFileForVersionByName(ctx context.Context, versionID int64, name, key string) (*PackageFile, error) { 92 if name == "" { 93 return nil, ErrPackageFileNotExist 94 } 95 96 pf := &PackageFile{ 97 VersionID: versionID, 98 LowerName: strings.ToLower(name), 99 CompositeKey: key, 100 } 101 102 has, err := db.GetEngine(ctx).Get(pf) 103 if err != nil { 104 return nil, err 105 } 106 if !has { 107 return nil, ErrPackageFileNotExist 108 } 109 return pf, nil 110 } 111 112 // DeleteFileByID deletes a file 113 func DeleteFileByID(ctx context.Context, fileID int64) error { 114 _, err := db.GetEngine(ctx).ID(fileID).Delete(&PackageFile{}) 115 return err 116 } 117 118 // PackageFileSearchOptions are options for SearchXXX methods 119 type PackageFileSearchOptions struct { 120 OwnerID int64 121 PackageType Type 122 VersionID int64 123 Query string 124 CompositeKey string 125 Properties map[string]string 126 OlderThan time.Duration 127 HashAlgorithm string 128 Hash string 129 db.Paginator 130 } 131 132 func (opts *PackageFileSearchOptions) toConds() builder.Cond { 133 cond := builder.NewCond() 134 135 if opts.VersionID != 0 { 136 cond = cond.And(builder.Eq{"package_file.version_id": opts.VersionID}) 137 } else if opts.OwnerID != 0 || (opts.PackageType != "" && opts.PackageType != "all") { 138 var versionCond builder.Cond = builder.Eq{ 139 "package_version.is_internal": false, 140 } 141 if opts.OwnerID != 0 { 142 versionCond = versionCond.And(builder.Eq{"package.owner_id": opts.OwnerID}) 143 } 144 if opts.PackageType != "" && opts.PackageType != "all" { 145 versionCond = versionCond.And(builder.Eq{"package.type": opts.PackageType}) 146 } 147 148 in := builder. 149 Select("package_version.id"). 150 From("package_version"). 151 InnerJoin("package", "package.id = package_version.package_id"). 152 Where(versionCond) 153 154 cond = cond.And(builder.In("package_file.version_id", in)) 155 } 156 if opts.CompositeKey != "" { 157 cond = cond.And(builder.Eq{"package_file.composite_key": opts.CompositeKey}) 158 } 159 if opts.Query != "" { 160 cond = cond.And(builder.Like{"package_file.lower_name", strings.ToLower(opts.Query)}) 161 } 162 163 if len(opts.Properties) != 0 { 164 var propsCond builder.Cond = builder.Eq{ 165 "package_property.ref_type": PropertyTypeFile, 166 } 167 propsCond = propsCond.And(builder.Expr("package_property.ref_id = package_file.id")) 168 169 propsCondBlock := builder.NewCond() 170 for name, value := range opts.Properties { 171 propsCondBlock = propsCondBlock.Or(builder.Eq{ 172 "package_property.name": name, 173 "package_property.value": value, 174 }) 175 } 176 propsCond = propsCond.And(propsCondBlock) 177 178 cond = cond.And(builder.Eq{ 179 strconv.Itoa(len(opts.Properties)): builder.Select("COUNT(*)").Where(propsCond).From("package_property"), 180 }) 181 } 182 183 if opts.OlderThan != 0 { 184 cond = cond.And(builder.Lt{"package_file.created_unix": time.Now().Add(-opts.OlderThan).Unix()}) 185 } 186 187 if opts.Hash != "" { 188 var field string 189 switch strings.ToLower(opts.HashAlgorithm) { 190 case "md5": 191 field = "package_blob.hash_md5" 192 case "sha1": 193 field = "package_blob.hash_sha1" 194 case "sha256": 195 field = "package_blob.hash_sha256" 196 case "sha512": 197 fallthrough 198 default: // default to SHA512 if not specified or unknown 199 field = "package_blob.hash_sha512" 200 } 201 innerCond := builder. 202 Expr("package_blob.id = package_file.blob_id"). 203 And(builder.Eq{field: opts.Hash}) 204 cond = cond.And(builder.Exists(builder.Select("package_blob.id").From("package_blob").Where(innerCond))) 205 } 206 207 return cond 208 } 209 210 // SearchFiles gets all files of packages matching the search options 211 func SearchFiles(ctx context.Context, opts *PackageFileSearchOptions) ([]*PackageFile, int64, error) { 212 sess := db.GetEngine(ctx). 213 Where(opts.toConds()) 214 215 if opts.Paginator != nil { 216 sess = db.SetSessionPagination(sess, opts) 217 } 218 219 pfs := make([]*PackageFile, 0, 10) 220 count, err := sess.FindAndCount(&pfs) 221 return pfs, count, err 222 } 223 224 // CalculateFileSize sums up all blob sizes matching the search options. 225 // It does NOT respect the deduplication of blobs. 226 func CalculateFileSize(ctx context.Context, opts *PackageFileSearchOptions) (int64, error) { 227 return db.GetEngine(ctx). 228 Table("package_file"). 229 Where(opts.toConds()). 230 Join("INNER", "package_blob", "package_blob.id = package_file.blob_id"). 231 SumInt(new(PackageBlob), "size") 232 }