code.gitea.io/gitea@v1.21.7/routers/api/packages/cran/cran.go (about) 1 // Copyright 2023 The Gitea Authors. All rights reserved. 2 // SPDX-License-Identifier: MIT 3 4 package cran 5 6 import ( 7 "compress/gzip" 8 "errors" 9 "fmt" 10 "io" 11 "net/http" 12 "strings" 13 14 packages_model "code.gitea.io/gitea/models/packages" 15 cran_model "code.gitea.io/gitea/models/packages/cran" 16 "code.gitea.io/gitea/modules/context" 17 packages_module "code.gitea.io/gitea/modules/packages" 18 cran_module "code.gitea.io/gitea/modules/packages/cran" 19 "code.gitea.io/gitea/modules/util" 20 "code.gitea.io/gitea/routers/api/packages/helper" 21 packages_service "code.gitea.io/gitea/services/packages" 22 ) 23 24 func apiError(ctx *context.Context, status int, obj any) { 25 helper.LogAndProcessError(ctx, status, obj, func(message string) { 26 ctx.PlainText(status, message) 27 }) 28 } 29 30 func EnumerateSourcePackages(ctx *context.Context) { 31 enumeratePackages(ctx, ctx.Params("format"), &cran_model.SearchOptions{ 32 OwnerID: ctx.Package.Owner.ID, 33 FileType: cran_module.TypeSource, 34 }) 35 } 36 37 func EnumerateBinaryPackages(ctx *context.Context) { 38 enumeratePackages(ctx, ctx.Params("format"), &cran_model.SearchOptions{ 39 OwnerID: ctx.Package.Owner.ID, 40 FileType: cran_module.TypeBinary, 41 Platform: ctx.Params("platform"), 42 RVersion: ctx.Params("rversion"), 43 }) 44 } 45 46 func enumeratePackages(ctx *context.Context, format string, opts *cran_model.SearchOptions) { 47 if format != "" && format != ".gz" { 48 apiError(ctx, http.StatusNotFound, nil) 49 return 50 } 51 52 pvs, err := cran_model.SearchLatestVersions(ctx, opts) 53 if err != nil { 54 apiError(ctx, http.StatusInternalServerError, err) 55 return 56 } 57 if len(pvs) == 0 { 58 apiError(ctx, http.StatusNotFound, nil) 59 return 60 } 61 62 pds, err := packages_model.GetPackageDescriptors(ctx, pvs) 63 if err != nil { 64 apiError(ctx, http.StatusInternalServerError, err) 65 return 66 } 67 68 var w io.Writer = ctx.Resp 69 70 if format == ".gz" { 71 ctx.Resp.Header().Set("Content-Type", "application/x-gzip") 72 73 gzw := gzip.NewWriter(w) 74 defer gzw.Close() 75 76 w = gzw 77 } else { 78 ctx.Resp.Header().Set("Content-Type", "text/plain;charset=utf-8") 79 } 80 ctx.Resp.WriteHeader(http.StatusOK) 81 82 for i, pd := range pds { 83 if i > 0 { 84 fmt.Fprintln(w) 85 } 86 87 var pfd *packages_model.PackageFileDescriptor 88 for _, d := range pd.Files { 89 if d.Properties.GetByName(cran_module.PropertyType) == opts.FileType && 90 d.Properties.GetByName(cran_module.PropertyPlatform) == opts.Platform && 91 d.Properties.GetByName(cran_module.PropertyRVersion) == opts.RVersion { 92 pfd = d 93 break 94 } 95 } 96 97 metadata := pd.Metadata.(*cran_module.Metadata) 98 99 fmt.Fprintln(w, "Package:", pd.Package.Name) 100 fmt.Fprintln(w, "Version:", pd.Version.Version) 101 if metadata.License != "" { 102 fmt.Fprintln(w, "License:", metadata.License) 103 } 104 if len(metadata.Depends) > 0 { 105 fmt.Fprintln(w, "Depends:", strings.Join(metadata.Depends, ", ")) 106 } 107 if len(metadata.Imports) > 0 { 108 fmt.Fprintln(w, "Imports:", strings.Join(metadata.Imports, ", ")) 109 } 110 if len(metadata.LinkingTo) > 0 { 111 fmt.Fprintln(w, "LinkingTo:", strings.Join(metadata.LinkingTo, ", ")) 112 } 113 if len(metadata.Suggests) > 0 { 114 fmt.Fprintln(w, "Suggests:", strings.Join(metadata.Suggests, ", ")) 115 } 116 needsCompilation := "no" 117 if metadata.NeedsCompilation { 118 needsCompilation = "yes" 119 } 120 fmt.Fprintln(w, "NeedsCompilation:", needsCompilation) 121 fmt.Fprintln(w, "MD5sum:", pfd.Blob.HashMD5) 122 } 123 } 124 125 func UploadSourcePackageFile(ctx *context.Context) { 126 uploadPackageFile( 127 ctx, 128 packages_model.EmptyFileKey, 129 map[string]string{ 130 cran_module.PropertyType: cran_module.TypeSource, 131 }, 132 ) 133 } 134 135 func UploadBinaryPackageFile(ctx *context.Context) { 136 platform, rversion := ctx.FormTrim("platform"), ctx.FormTrim("rversion") 137 if platform == "" || rversion == "" { 138 apiError(ctx, http.StatusBadRequest, nil) 139 return 140 } 141 142 uploadPackageFile( 143 ctx, 144 platform+"|"+rversion, 145 map[string]string{ 146 cran_module.PropertyType: cran_module.TypeBinary, 147 cran_module.PropertyPlatform: platform, 148 cran_module.PropertyRVersion: rversion, 149 }, 150 ) 151 } 152 153 func uploadPackageFile(ctx *context.Context, compositeKey string, properties map[string]string) { 154 upload, close, err := ctx.UploadStream() 155 if err != nil { 156 apiError(ctx, http.StatusBadRequest, err) 157 return 158 } 159 if close { 160 defer upload.Close() 161 } 162 163 buf, err := packages_module.CreateHashedBufferFromReader(upload) 164 if err != nil { 165 apiError(ctx, http.StatusInternalServerError, err) 166 return 167 } 168 defer buf.Close() 169 170 pck, err := cran_module.ParsePackage(buf, buf.Size()) 171 if err != nil { 172 if errors.Is(err, util.ErrInvalidArgument) { 173 apiError(ctx, http.StatusBadRequest, err) 174 } else { 175 apiError(ctx, http.StatusInternalServerError, err) 176 } 177 return 178 } 179 180 if _, err := buf.Seek(0, io.SeekStart); err != nil { 181 apiError(ctx, http.StatusInternalServerError, err) 182 return 183 } 184 185 _, _, err = packages_service.CreatePackageOrAddFileToExisting( 186 ctx, 187 &packages_service.PackageCreationInfo{ 188 PackageInfo: packages_service.PackageInfo{ 189 Owner: ctx.Package.Owner, 190 PackageType: packages_model.TypeCran, 191 Name: pck.Name, 192 Version: pck.Version, 193 }, 194 SemverCompatible: false, 195 Creator: ctx.Doer, 196 Metadata: pck.Metadata, 197 }, 198 &packages_service.PackageFileCreationInfo{ 199 PackageFileInfo: packages_service.PackageFileInfo{ 200 Filename: fmt.Sprintf("%s_%s%s", pck.Name, pck.Version, pck.FileExtension), 201 CompositeKey: compositeKey, 202 }, 203 Creator: ctx.Doer, 204 Data: buf, 205 IsLead: true, 206 Properties: properties, 207 }, 208 ) 209 if err != nil { 210 switch err { 211 case packages_model.ErrDuplicatePackageFile: 212 apiError(ctx, http.StatusConflict, err) 213 case packages_service.ErrQuotaTotalCount, packages_service.ErrQuotaTypeSize, packages_service.ErrQuotaTotalSize: 214 apiError(ctx, http.StatusForbidden, err) 215 default: 216 apiError(ctx, http.StatusInternalServerError, err) 217 } 218 return 219 } 220 221 ctx.Status(http.StatusCreated) 222 } 223 224 func DownloadSourcePackageFile(ctx *context.Context) { 225 downloadPackageFile(ctx, &cran_model.SearchOptions{ 226 OwnerID: ctx.Package.Owner.ID, 227 FileType: cran_module.TypeSource, 228 Filename: ctx.Params("filename"), 229 }) 230 } 231 232 func DownloadBinaryPackageFile(ctx *context.Context) { 233 downloadPackageFile(ctx, &cran_model.SearchOptions{ 234 OwnerID: ctx.Package.Owner.ID, 235 FileType: cran_module.TypeBinary, 236 Platform: ctx.Params("platform"), 237 RVersion: ctx.Params("rversion"), 238 Filename: ctx.Params("filename"), 239 }) 240 } 241 242 func downloadPackageFile(ctx *context.Context, opts *cran_model.SearchOptions) { 243 pf, err := cran_model.SearchFile(ctx, opts) 244 if err != nil { 245 if errors.Is(err, util.ErrNotExist) { 246 apiError(ctx, http.StatusNotFound, err) 247 } else { 248 apiError(ctx, http.StatusInternalServerError, err) 249 } 250 return 251 } 252 253 s, u, _, err := packages_service.GetPackageFileStream(ctx, pf) 254 if err != nil { 255 if errors.Is(err, util.ErrNotExist) { 256 apiError(ctx, http.StatusNotFound, err) 257 } else { 258 apiError(ctx, http.StatusInternalServerError, err) 259 } 260 return 261 } 262 263 helper.ServePackageFile(ctx, s, u, pf) 264 }