2022-03-30 08:42:47 +00:00
|
|
|
// Copyright 2021 The Gitea Authors. All rights reserved.
|
2022-11-27 18:20:29 +00:00
|
|
|
// SPDX-License-Identifier: MIT
|
2022-03-30 08:42:47 +00:00
|
|
|
|
|
|
|
package packages
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"code.gitea.io/gitea/models/db"
|
|
|
|
"code.gitea.io/gitea/modules/timeutil"
|
2022-12-31 11:49:37 +00:00
|
|
|
"code.gitea.io/gitea/modules/util"
|
2022-03-30 08:42:47 +00:00
|
|
|
|
|
|
|
"xorm.io/builder"
|
|
|
|
)
|
|
|
|
|
|
|
|
func init() {
|
|
|
|
db.RegisterModel(new(PackageFile))
|
|
|
|
}
|
|
|
|
|
|
|
|
var (
|
|
|
|
// ErrDuplicatePackageFile indicates a duplicated package file error
|
2022-12-31 11:49:37 +00:00
|
|
|
ErrDuplicatePackageFile = util.NewAlreadyExistErrorf("package file already exists")
|
2022-03-30 08:42:47 +00:00
|
|
|
// ErrPackageFileNotExist indicates a package file not exist error
|
2022-12-31 11:49:37 +00:00
|
|
|
ErrPackageFileNotExist = util.NewNotExistErrorf("package file does not exist")
|
2022-03-30 08:42:47 +00: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)
|
|
|
|
|
|
|
|
key := &PackageFile{
|
|
|
|
VersionID: pf.VersionID,
|
|
|
|
LowerName: pf.LowerName,
|
|
|
|
CompositeKey: pf.CompositeKey,
|
|
|
|
}
|
|
|
|
|
|
|
|
has, err := e.Get(key)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if has {
|
|
|
|
return pf, ErrDuplicatePackageFile
|
|
|
|
}
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
pf := &PackageFile{
|
|
|
|
VersionID: versionID,
|
|
|
|
LowerName: strings.ToLower(name),
|
|
|
|
CompositeKey: key,
|
|
|
|
}
|
|
|
|
|
|
|
|
has, err := db.GetEngine(ctx).Get(pf)
|
|
|
|
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 16:31:35 +00:00
|
|
|
OwnerID int64
|
2023-05-05 20:33:37 +00:00
|
|
|
PackageType Type
|
2023-05-02 16:31:35 +00:00
|
|
|
VersionID int64
|
|
|
|
Query string
|
|
|
|
CompositeKey string
|
|
|
|
Properties map[string]string
|
|
|
|
OlderThan time.Duration
|
|
|
|
HashAlgorithm string
|
|
|
|
Hash string
|
2022-03-30 08:42:47 +00: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 01:32:09 +00:00
|
|
|
InnerJoin("package", "package.id = package_version.package_id").
|
2022-03-30 08:42:47 +00: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 16:31:35 +00: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 08:42:47 +00: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 06:34:27 +00:00
|
|
|
|
2023-01-18 15:52:04 +00:00
|
|
|
// CalculateFileSize sums up all blob sizes matching the search options.
|
2022-11-09 06:34:27 +00:00
|
|
|
// It does NOT respect the deduplication of blobs.
|
2023-01-18 15:52:04 +00:00
|
|
|
func CalculateFileSize(ctx context.Context, opts *PackageFileSearchOptions) (int64, error) {
|
2022-11-09 06:34:27 +00: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")
|
|
|
|
}
|