gitea源码

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351
  1. // Copyright 2021 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package packages
  4. import (
  5. "context"
  6. "strconv"
  7. "strings"
  8. "code.gitea.io/gitea/models/db"
  9. "code.gitea.io/gitea/modules/optional"
  10. "code.gitea.io/gitea/modules/timeutil"
  11. "code.gitea.io/gitea/modules/util"
  12. "xorm.io/builder"
  13. "xorm.io/xorm"
  14. )
  15. // ErrDuplicatePackageVersion indicates a duplicated package version error
  16. var ErrDuplicatePackageVersion = util.NewAlreadyExistErrorf("package version already exists")
  17. func init() {
  18. db.RegisterModel(new(PackageVersion))
  19. }
  20. // PackageVersion represents a package version
  21. type PackageVersion struct {
  22. ID int64 `xorm:"pk autoincr"`
  23. PackageID int64 `xorm:"UNIQUE(s) INDEX NOT NULL"`
  24. CreatorID int64 `xorm:"NOT NULL DEFAULT 0"`
  25. Version string `xorm:"NOT NULL"`
  26. LowerVersion string `xorm:"UNIQUE(s) INDEX NOT NULL"`
  27. CreatedUnix timeutil.TimeStamp `xorm:"created INDEX NOT NULL"`
  28. IsInternal bool `xorm:"INDEX NOT NULL DEFAULT false"`
  29. MetadataJSON string `xorm:"metadata_json LONGTEXT"`
  30. DownloadCount int64 `xorm:"NOT NULL DEFAULT 0"`
  31. }
  32. // IsPrerelease checks if the version is a prerelease version according to semantic versioning
  33. func (pv *PackageVersion) IsPrerelease() bool {
  34. if pv == nil || pv.Version == "" {
  35. return false
  36. }
  37. return strings.Contains(pv.Version, "-")
  38. }
  39. // GetOrInsertVersion inserts a version. If the same version exist already ErrDuplicatePackageVersion is returned
  40. func GetOrInsertVersion(ctx context.Context, pv *PackageVersion) (*PackageVersion, error) {
  41. e := db.GetEngine(ctx)
  42. existing := &PackageVersion{}
  43. has, err := e.Where(builder.Eq{
  44. "package_id": pv.PackageID,
  45. "lower_version": pv.LowerVersion,
  46. }).Get(existing)
  47. if err != nil {
  48. return nil, err
  49. }
  50. if has {
  51. return existing, ErrDuplicatePackageVersion
  52. }
  53. if _, err = e.Insert(pv); err != nil {
  54. return nil, err
  55. }
  56. return pv, nil
  57. }
  58. // UpdateVersion updates a version
  59. func UpdateVersion(ctx context.Context, pv *PackageVersion) error {
  60. _, err := db.GetEngine(ctx).ID(pv.ID).Update(pv)
  61. return err
  62. }
  63. // IncrementDownloadCounter increments the download counter of a version
  64. func IncrementDownloadCounter(ctx context.Context, versionID int64) error {
  65. _, err := db.GetEngine(ctx).Exec("UPDATE `package_version` SET `download_count` = `download_count` + 1 WHERE `id` = ?", versionID)
  66. return err
  67. }
  68. // GetVersionByID gets a version by id
  69. func GetVersionByID(ctx context.Context, versionID int64) (*PackageVersion, error) {
  70. pv := &PackageVersion{}
  71. has, err := db.GetEngine(ctx).ID(versionID).Get(pv)
  72. if err != nil {
  73. return nil, err
  74. }
  75. if !has {
  76. return nil, ErrPackageNotExist
  77. }
  78. return pv, nil
  79. }
  80. // GetVersionByNameAndVersion gets a version by name and version number
  81. func GetVersionByNameAndVersion(ctx context.Context, ownerID int64, packageType Type, name, version string) (*PackageVersion, error) {
  82. return getVersionByNameAndVersion(ctx, ownerID, packageType, name, version, false)
  83. }
  84. // GetInternalVersionByNameAndVersion gets a version by name and version number
  85. func GetInternalVersionByNameAndVersion(ctx context.Context, ownerID int64, packageType Type, name, version string) (*PackageVersion, error) {
  86. return getVersionByNameAndVersion(ctx, ownerID, packageType, name, version, true)
  87. }
  88. func getVersionByNameAndVersion(ctx context.Context, ownerID int64, packageType Type, name, version string, isInternal bool) (*PackageVersion, error) {
  89. pvs, _, err := SearchVersions(ctx, &PackageSearchOptions{
  90. OwnerID: ownerID,
  91. Type: packageType,
  92. Name: SearchValue{
  93. ExactMatch: true,
  94. Value: name,
  95. },
  96. Version: SearchValue{
  97. ExactMatch: true,
  98. Value: version,
  99. },
  100. IsInternal: optional.Some(isInternal),
  101. Paginator: db.NewAbsoluteListOptions(0, 1),
  102. })
  103. if err != nil {
  104. return nil, err
  105. }
  106. if len(pvs) == 0 {
  107. return nil, ErrPackageNotExist
  108. }
  109. return pvs[0], nil
  110. }
  111. // GetVersionsByPackageType gets all versions of a specific type
  112. func GetVersionsByPackageType(ctx context.Context, ownerID int64, packageType Type) ([]*PackageVersion, error) {
  113. pvs, _, err := SearchVersions(ctx, &PackageSearchOptions{
  114. OwnerID: ownerID,
  115. Type: packageType,
  116. IsInternal: optional.Some(false),
  117. })
  118. return pvs, err
  119. }
  120. // GetVersionsByPackageName gets all versions of a specific package
  121. func GetVersionsByPackageName(ctx context.Context, ownerID int64, packageType Type, name string) ([]*PackageVersion, error) {
  122. pvs, _, err := SearchVersions(ctx, &PackageSearchOptions{
  123. OwnerID: ownerID,
  124. Type: packageType,
  125. Name: SearchValue{
  126. ExactMatch: true,
  127. Value: name,
  128. },
  129. IsInternal: optional.Some(false),
  130. })
  131. return pvs, err
  132. }
  133. // DeleteVersionByID deletes a version by id
  134. func DeleteVersionByID(ctx context.Context, versionID int64) error {
  135. _, err := db.GetEngine(ctx).ID(versionID).Delete(&PackageVersion{})
  136. return err
  137. }
  138. // HasVersionFileReferences checks if there are associated files
  139. func HasVersionFileReferences(ctx context.Context, versionID int64) (bool, error) {
  140. return db.GetEngine(ctx).Get(&PackageFile{
  141. VersionID: versionID,
  142. })
  143. }
  144. // SearchValue describes a value to search
  145. // If ExactMatch is true, the field must match the value otherwise a LIKE search is performed.
  146. type SearchValue struct {
  147. Value string
  148. ExactMatch bool
  149. }
  150. type VersionSort = string
  151. const (
  152. SortNameAsc VersionSort = "name_asc"
  153. SortNameDesc VersionSort = "name_desc"
  154. SortVersionAsc VersionSort = "version_asc"
  155. SortVersionDesc VersionSort = "version_desc"
  156. SortCreatedAsc VersionSort = "created_asc"
  157. SortCreatedDesc VersionSort = "created_desc"
  158. )
  159. // PackageSearchOptions are options for SearchXXX methods
  160. // All fields optional and are not used if they have their default value (nil, "", 0)
  161. type PackageSearchOptions struct {
  162. OwnerID int64
  163. RepoID int64
  164. Type Type
  165. PackageID int64
  166. Name SearchValue // only results with the specific name are found
  167. Version SearchValue // only results with the specific version are found
  168. Properties map[string]string // only results are found which contain all listed version properties with the specific value
  169. IsInternal optional.Option[bool]
  170. HasFileWithName string // only results are found which are associated with a file with the specific name
  171. HasFiles optional.Option[bool] // only results are found which have associated files
  172. Sort VersionSort
  173. Paginator db.Paginator
  174. }
  175. func (opts *PackageSearchOptions) ToConds() builder.Cond {
  176. cond := builder.NewCond()
  177. if opts.IsInternal.Has() {
  178. cond = builder.Eq{
  179. "package_version.is_internal": opts.IsInternal.Value(),
  180. }
  181. }
  182. if opts.OwnerID != 0 {
  183. cond = cond.And(builder.Eq{"package.owner_id": opts.OwnerID})
  184. }
  185. if opts.RepoID != 0 {
  186. cond = cond.And(builder.Eq{"package.repo_id": opts.RepoID})
  187. }
  188. if opts.Type != "" && opts.Type != "all" {
  189. cond = cond.And(builder.Eq{"package.type": opts.Type})
  190. }
  191. if opts.PackageID != 0 {
  192. cond = cond.And(builder.Eq{"package.id": opts.PackageID})
  193. }
  194. if opts.Name.Value != "" {
  195. if opts.Name.ExactMatch {
  196. cond = cond.And(builder.Eq{"package.lower_name": strings.ToLower(opts.Name.Value)})
  197. } else {
  198. cond = cond.And(builder.Like{"package.lower_name", strings.ToLower(opts.Name.Value)})
  199. }
  200. }
  201. if opts.Version.Value != "" {
  202. if opts.Version.ExactMatch {
  203. cond = cond.And(builder.Eq{"package_version.lower_version": strings.ToLower(opts.Version.Value)})
  204. } else {
  205. cond = cond.And(builder.Like{"package_version.lower_version", strings.ToLower(opts.Version.Value)})
  206. }
  207. }
  208. if len(opts.Properties) != 0 {
  209. var propsCond builder.Cond = builder.Eq{
  210. "package_property.ref_type": PropertyTypeVersion,
  211. }
  212. propsCond = propsCond.And(builder.Expr("package_property.ref_id = package_version.id"))
  213. propsCondBlock := builder.NewCond()
  214. for name, value := range opts.Properties {
  215. propsCondBlock = propsCondBlock.Or(builder.Eq{
  216. "package_property.name": name,
  217. "package_property.value": value,
  218. })
  219. }
  220. propsCond = propsCond.And(propsCondBlock)
  221. cond = cond.And(builder.Eq{
  222. strconv.Itoa(len(opts.Properties)): builder.Select("COUNT(*)").Where(propsCond).From("package_property"),
  223. })
  224. }
  225. if opts.HasFileWithName != "" {
  226. fileCond := builder.Expr("package_file.version_id = package_version.id").And(builder.Eq{"package_file.lower_name": strings.ToLower(opts.HasFileWithName)})
  227. cond = cond.And(builder.Exists(builder.Select("package_file.id").From("package_file").Where(fileCond)))
  228. }
  229. if opts.HasFiles.Has() {
  230. filesCond := builder.Exists(builder.Select("package_file.id").From("package_file").Where(builder.Expr("package_file.version_id = package_version.id")))
  231. if !opts.HasFiles.Value() {
  232. filesCond = builder.Not{filesCond}
  233. }
  234. cond = cond.And(filesCond)
  235. }
  236. return cond
  237. }
  238. func (opts *PackageSearchOptions) configureOrderBy(e db.Engine) {
  239. switch opts.Sort {
  240. case SortNameAsc:
  241. e.Asc("package.name")
  242. case SortNameDesc:
  243. e.Desc("package.name")
  244. case SortVersionDesc:
  245. e.Desc("package_version.version")
  246. case SortVersionAsc:
  247. e.Asc("package_version.version")
  248. case SortCreatedAsc:
  249. e.Asc("package_version.created_unix")
  250. default:
  251. e.Desc("package_version.created_unix")
  252. }
  253. e.Desc("package_version.id") // Sort by id for stable order with duplicates in the other field
  254. }
  255. func searchVersionsBySession(sess *xorm.Session, opts *PackageSearchOptions) ([]*PackageVersion, int64, error) {
  256. opts.configureOrderBy(sess)
  257. pvs := make([]*PackageVersion, 0, 10)
  258. if opts.Paginator != nil {
  259. sess = db.SetSessionPagination(sess, opts.Paginator)
  260. count, err := sess.FindAndCount(&pvs)
  261. return pvs, count, err
  262. }
  263. err := sess.Find(&pvs)
  264. return pvs, int64(len(pvs)), err
  265. }
  266. // SearchVersions gets all versions of packages matching the search options
  267. func SearchVersions(ctx context.Context, opts *PackageSearchOptions) ([]*PackageVersion, int64, error) {
  268. sess := db.GetEngine(ctx).
  269. Select("package_version.*").
  270. Table("package_version").
  271. Join("INNER", "package", "package.id = package_version.package_id").
  272. Where(opts.ToConds())
  273. return searchVersionsBySession(sess, opts)
  274. }
  275. // SearchLatestVersions gets the latest version of every package matching the search options
  276. func SearchLatestVersions(ctx context.Context, opts *PackageSearchOptions) ([]*PackageVersion, int64, error) {
  277. in := builder.
  278. Select("MAX(package_version.id)").
  279. From("package_version").
  280. InnerJoin("package", "package.id = package_version.package_id").
  281. Where(opts.ToConds()).
  282. GroupBy("package_version.package_id")
  283. sess := db.GetEngine(ctx).
  284. Select("package_version.*").
  285. Table("package_version").
  286. Join("INNER", "package", "package.id = package_version.package_id").
  287. Where(builder.In("package_version.id", in))
  288. return searchVersionsBySession(sess, opts)
  289. }
  290. // ExistVersion checks if a version matching the search options exist
  291. func ExistVersion(ctx context.Context, opts *PackageSearchOptions) (bool, error) {
  292. return db.GetEngine(ctx).
  293. Where(opts.ToConds()).
  294. Table("package_version").
  295. Join("INNER", "package", "package.id = package_version.package_id").
  296. Exist(new(PackageVersion))
  297. }
  298. // CountVersions counts all versions of packages matching the search options
  299. func CountVersions(ctx context.Context, opts *PackageSearchOptions) (int64, error) {
  300. return db.GetEngine(ctx).
  301. Where(opts.ToConds()).
  302. Table("package_version").
  303. Join("INNER", "package", "package.id = package_version.package_id").
  304. Count(new(PackageVersion))
  305. }