選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

conda.go 7.8KB

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