gitea源码

conda.go 8.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323
  1. // Copyright 2022 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package conda
  4. import (
  5. "errors"
  6. "fmt"
  7. "io"
  8. "net/http"
  9. "strings"
  10. packages_model "code.gitea.io/gitea/models/packages"
  11. conda_model "code.gitea.io/gitea/models/packages/conda"
  12. "code.gitea.io/gitea/modules/json"
  13. packages_module "code.gitea.io/gitea/modules/packages"
  14. conda_module "code.gitea.io/gitea/modules/packages/conda"
  15. "code.gitea.io/gitea/modules/util"
  16. "code.gitea.io/gitea/routers/api/packages/helper"
  17. "code.gitea.io/gitea/services/context"
  18. packages_service "code.gitea.io/gitea/services/packages"
  19. "github.com/dsnet/compress/bzip2"
  20. )
  21. func apiError(ctx *context.Context, status int, obj any) {
  22. message := helper.ProcessErrorForUser(ctx, status, obj)
  23. ctx.JSON(status, struct {
  24. Reason string `json:"reason"`
  25. Message string `json:"message"`
  26. }{
  27. Reason: http.StatusText(status),
  28. Message: message,
  29. })
  30. }
  31. func isCondaPackageFileName(filename string) bool {
  32. return strings.HasSuffix(filename, ".tar.bz2") || strings.HasSuffix(filename, ".conda")
  33. }
  34. func ListOrGetPackages(ctx *context.Context) {
  35. filename := ctx.PathParam("filename")
  36. switch filename {
  37. case "repodata.json", "repodata.json.bz2", "current_repodata.json", "current_repodata.json.bz2":
  38. EnumeratePackages(ctx)
  39. return
  40. }
  41. if isCondaPackageFileName(filename) {
  42. DownloadPackageFile(ctx)
  43. return
  44. }
  45. http.NotFound(ctx.Resp, ctx.Req)
  46. }
  47. func EnumeratePackages(ctx *context.Context) {
  48. type Info struct {
  49. Subdir string `json:"subdir"`
  50. }
  51. type PackageInfo struct {
  52. Name string `json:"name"`
  53. Version string `json:"version"`
  54. NoArch string `json:"noarch"`
  55. Subdir string `json:"subdir"`
  56. Timestamp int64 `json:"timestamp"`
  57. Build string `json:"build"`
  58. BuildNumber int64 `json:"build_number"`
  59. Dependencies []string `json:"depends"`
  60. License string `json:"license"`
  61. LicenseFamily string `json:"license_family"`
  62. HashMD5 string `json:"md5"`
  63. HashSHA256 string `json:"sha256"`
  64. Size int64 `json:"size"`
  65. }
  66. type RepoData struct {
  67. Info Info `json:"info"`
  68. Packages map[string]*PackageInfo `json:"packages"`
  69. PackagesConda map[string]*PackageInfo `json:"packages.conda"`
  70. Removed map[string]*PackageInfo `json:"removed"`
  71. }
  72. repoData := &RepoData{
  73. Info: Info{
  74. Subdir: ctx.PathParam("architecture"),
  75. },
  76. Packages: make(map[string]*PackageInfo),
  77. PackagesConda: make(map[string]*PackageInfo),
  78. Removed: make(map[string]*PackageInfo),
  79. }
  80. pfs, err := conda_model.SearchFiles(ctx, &conda_model.FileSearchOptions{
  81. OwnerID: ctx.Package.Owner.ID,
  82. Channel: ctx.PathParam("channel"),
  83. Subdir: repoData.Info.Subdir,
  84. })
  85. if err != nil {
  86. apiError(ctx, http.StatusInternalServerError, err)
  87. return
  88. }
  89. if len(pfs) == 0 {
  90. apiError(ctx, http.StatusNotFound, nil)
  91. return
  92. }
  93. pds := make(map[int64]*packages_model.PackageDescriptor)
  94. for _, pf := range pfs {
  95. pd, exists := pds[pf.VersionID]
  96. if !exists {
  97. pv, err := packages_model.GetVersionByID(ctx, pf.VersionID)
  98. if err != nil {
  99. apiError(ctx, http.StatusInternalServerError, err)
  100. return
  101. }
  102. pd, err = packages_model.GetPackageDescriptor(ctx, pv)
  103. if err != nil {
  104. apiError(ctx, http.StatusInternalServerError, err)
  105. return
  106. }
  107. pds[pf.VersionID] = pd
  108. }
  109. var pfd *packages_model.PackageFileDescriptor
  110. for _, d := range pd.Files {
  111. if d.File.ID == pf.ID {
  112. pfd = d
  113. break
  114. }
  115. }
  116. var fileMetadata *conda_module.FileMetadata
  117. if err := json.Unmarshal([]byte(pfd.Properties.GetByName(conda_module.PropertyMetadata)), &fileMetadata); err != nil {
  118. apiError(ctx, http.StatusInternalServerError, err)
  119. return
  120. }
  121. versionMetadata := pd.Metadata.(*conda_module.VersionMetadata)
  122. pi := &PackageInfo{
  123. Name: pd.PackageProperties.GetByName(conda_module.PropertyName),
  124. Version: pd.Version.Version,
  125. NoArch: fileMetadata.NoArch,
  126. Subdir: repoData.Info.Subdir,
  127. Timestamp: fileMetadata.Timestamp,
  128. Build: fileMetadata.Build,
  129. BuildNumber: fileMetadata.BuildNumber,
  130. Dependencies: fileMetadata.Dependencies,
  131. License: versionMetadata.License,
  132. LicenseFamily: versionMetadata.LicenseFamily,
  133. HashMD5: pfd.Blob.HashMD5,
  134. HashSHA256: pfd.Blob.HashSHA256,
  135. Size: pfd.Blob.Size,
  136. }
  137. if fileMetadata.IsCondaPackage {
  138. repoData.PackagesConda[pfd.File.Name] = pi
  139. } else {
  140. repoData.Packages[pfd.File.Name] = pi
  141. }
  142. }
  143. resp := ctx.Resp
  144. var w io.Writer = resp
  145. if strings.HasSuffix(ctx.PathParam("filename"), ".json") {
  146. resp.Header().Set("Content-Type", "application/json")
  147. } else {
  148. resp.Header().Set("Content-Type", "application/x-bzip2")
  149. zw, err := bzip2.NewWriter(w, nil)
  150. if err != nil {
  151. apiError(ctx, http.StatusInternalServerError, err)
  152. return
  153. }
  154. defer zw.Close()
  155. w = zw
  156. }
  157. resp.WriteHeader(http.StatusOK)
  158. _ = json.NewEncoder(w).Encode(repoData)
  159. }
  160. func UploadPackageFile(ctx *context.Context) {
  161. filename := ctx.PathParam("filename")
  162. if !isCondaPackageFileName(filename) {
  163. apiError(ctx, http.StatusBadRequest, nil)
  164. return
  165. }
  166. upload, needToClose, err := ctx.UploadStream()
  167. if err != nil {
  168. apiError(ctx, http.StatusInternalServerError, err)
  169. return
  170. }
  171. if needToClose {
  172. defer upload.Close()
  173. }
  174. buf, err := packages_module.CreateHashedBufferFromReader(upload)
  175. if err != nil {
  176. apiError(ctx, http.StatusInternalServerError, err)
  177. return
  178. }
  179. defer buf.Close()
  180. var pck *conda_module.Package
  181. if strings.HasSuffix(filename, ".tar.bz2") {
  182. pck, err = conda_module.ParsePackageBZ2(buf)
  183. } else {
  184. pck, err = conda_module.ParsePackageConda(buf, buf.Size())
  185. }
  186. if err != nil {
  187. if errors.Is(err, util.ErrInvalidArgument) {
  188. apiError(ctx, http.StatusBadRequest, err)
  189. } else {
  190. apiError(ctx, http.StatusInternalServerError, err)
  191. }
  192. return
  193. }
  194. if _, err := buf.Seek(0, io.SeekStart); err != nil {
  195. apiError(ctx, http.StatusInternalServerError, err)
  196. return
  197. }
  198. fullName := pck.Name
  199. channel := ctx.PathParam("channel")
  200. if channel != "" {
  201. fullName = channel + "/" + pck.Name
  202. }
  203. extension := ".tar.bz2"
  204. if pck.FileMetadata.IsCondaPackage {
  205. extension = ".conda"
  206. }
  207. fileMetadataRaw, err := json.Marshal(pck.FileMetadata)
  208. if err != nil {
  209. apiError(ctx, http.StatusInternalServerError, err)
  210. return
  211. }
  212. _, _, err = packages_service.CreatePackageOrAddFileToExisting(
  213. ctx,
  214. &packages_service.PackageCreationInfo{
  215. PackageInfo: packages_service.PackageInfo{
  216. Owner: ctx.Package.Owner,
  217. PackageType: packages_model.TypeConda,
  218. Name: fullName,
  219. Version: pck.Version,
  220. },
  221. SemverCompatible: false,
  222. Creator: ctx.Doer,
  223. Metadata: pck.VersionMetadata,
  224. PackageProperties: map[string]string{
  225. conda_module.PropertyName: pck.Name,
  226. conda_module.PropertyChannel: channel,
  227. },
  228. },
  229. &packages_service.PackageFileCreationInfo{
  230. PackageFileInfo: packages_service.PackageFileInfo{
  231. Filename: fmt.Sprintf("%s-%s-%s%s", pck.Name, pck.Version, pck.FileMetadata.Build, extension),
  232. CompositeKey: pck.Subdir,
  233. },
  234. Creator: ctx.Doer,
  235. Data: buf,
  236. IsLead: true,
  237. Properties: map[string]string{
  238. conda_module.PropertySubdir: pck.Subdir,
  239. conda_module.PropertyMetadata: string(fileMetadataRaw),
  240. },
  241. },
  242. )
  243. if err != nil {
  244. switch err {
  245. case packages_model.ErrDuplicatePackageFile:
  246. apiError(ctx, http.StatusConflict, err)
  247. case packages_service.ErrQuotaTotalCount, packages_service.ErrQuotaTypeSize, packages_service.ErrQuotaTotalSize:
  248. apiError(ctx, http.StatusForbidden, err)
  249. default:
  250. apiError(ctx, http.StatusInternalServerError, err)
  251. }
  252. return
  253. }
  254. ctx.Status(http.StatusCreated)
  255. }
  256. func DownloadPackageFile(ctx *context.Context) {
  257. pfs, err := conda_model.SearchFiles(ctx, &conda_model.FileSearchOptions{
  258. OwnerID: ctx.Package.Owner.ID,
  259. Channel: ctx.PathParam("channel"),
  260. Subdir: ctx.PathParam("architecture"),
  261. Filename: ctx.PathParam("filename"),
  262. })
  263. if err != nil {
  264. apiError(ctx, http.StatusInternalServerError, err)
  265. return
  266. }
  267. if len(pfs) != 1 {
  268. apiError(ctx, http.StatusNotFound, nil)
  269. return
  270. }
  271. pf := pfs[0]
  272. s, u, _, err := packages_service.OpenFileForDownload(ctx, pf, ctx.Req.Method)
  273. if err != nil {
  274. apiError(ctx, http.StatusInternalServerError, err)
  275. return
  276. }
  277. helper.ServePackageFile(ctx, s, u, pf)
  278. }