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  }