0
Fork 0
mirror of https://codeberg.org/forgejo/forgejo.git synced 2024-12-30 11:23:56 -05:00
forgejo/routers/api/packages/cran/cran.go

267 lines
6.9 KiB
Go

// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package cran
import (
"compress/gzip"
"errors"
"fmt"
"io"
"net/http"
"strings"
packages_model "code.gitea.io/gitea/models/packages"
cran_model "code.gitea.io/gitea/models/packages/cran"
"code.gitea.io/gitea/modules/context"
packages_module "code.gitea.io/gitea/modules/packages"
cran_module "code.gitea.io/gitea/modules/packages/cran"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers/api/packages/helper"
packages_service "code.gitea.io/gitea/services/packages"
)
func apiError(ctx *context.Context, status int, obj interface{}) {
helper.LogAndProcessError(ctx, status, obj, func(message string) {
ctx.PlainText(status, message)
})
}
func EnumerateSourcePackages(ctx *context.Context) {
enumeratePackages(ctx, ctx.Params("format"), &cran_model.SearchOptions{
OwnerID: ctx.Package.Owner.ID,
FileType: cran_module.TypeSource,
})
}
func EnumerateBinaryPackages(ctx *context.Context) {
enumeratePackages(ctx, ctx.Params("format"), &cran_model.SearchOptions{
OwnerID: ctx.Package.Owner.ID,
FileType: cran_module.TypeBinary,
Platform: ctx.Params("platform"),
RVersion: ctx.Params("rversion"),
})
}
func enumeratePackages(ctx *context.Context, format string, opts *cran_model.SearchOptions) {
if format != "" && format != ".gz" {
apiError(ctx, http.StatusNotFound, nil)
return
}
pvs, err := cran_model.SearchLatestVersions(ctx, opts)
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
}
if len(pvs) == 0 {
apiError(ctx, http.StatusNotFound, nil)
return
}
pds, err := packages_model.GetPackageDescriptors(ctx, pvs)
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
}
var w io.Writer = ctx.Resp
if format == ".gz" {
ctx.Resp.Header().Set("Content-Type", "application/x-gzip")
gzw := gzip.NewWriter(w)
defer gzw.Close()
w = gzw
} else {
ctx.Resp.Header().Set("Content-Type", "text/plain;charset=utf-8")
}
ctx.Resp.WriteHeader(http.StatusOK)
for i, pd := range pds {
if i > 0 {
fmt.Fprintln(w)
}
var pfd *packages_model.PackageFileDescriptor
for _, d := range pd.Files {
if d.Properties.GetByName(cran_module.PropertyType) == opts.FileType &&
d.Properties.GetByName(cran_module.PropertyPlatform) == opts.Platform &&
d.Properties.GetByName(cran_module.PropertyRVersion) == opts.RVersion {
pfd = d
break
}
}
metadata := pd.Metadata.(*cran_module.Metadata)
fmt.Fprintln(w, "Package:", pd.Package.Name)
fmt.Fprintln(w, "Version:", pd.Version.Version)
if metadata.License != "" {
fmt.Fprintln(w, "License:", metadata.License)
}
if len(metadata.Depends) > 0 {
fmt.Fprintln(w, "Depends:", strings.Join(metadata.Depends, ", "))
}
if len(metadata.Imports) > 0 {
fmt.Fprintln(w, "Imports:", strings.Join(metadata.Imports, ", "))
}
if len(metadata.LinkingTo) > 0 {
fmt.Fprintln(w, "LinkingTo:", strings.Join(metadata.LinkingTo, ", "))
}
if len(metadata.Suggests) > 0 {
fmt.Fprintln(w, "Suggests:", strings.Join(metadata.Suggests, ", "))
}
needsCompilation := "no"
if metadata.NeedsCompilation {
needsCompilation = "yes"
}
fmt.Fprintln(w, "NeedsCompilation:", needsCompilation)
fmt.Fprintln(w, "MD5sum:", pfd.Blob.HashMD5)
}
}
func UploadSourcePackageFile(ctx *context.Context) {
uploadPackageFile(
ctx,
packages_model.EmptyFileKey,
map[string]string{
cran_module.PropertyType: cran_module.TypeSource,
},
)
}
func UploadBinaryPackageFile(ctx *context.Context) {
platform, rversion := ctx.FormTrim("platform"), ctx.FormTrim("rversion")
if platform == "" || rversion == "" {
apiError(ctx, http.StatusBadRequest, nil)
return
}
uploadPackageFile(
ctx,
platform+"|"+rversion,
map[string]string{
cran_module.PropertyType: cran_module.TypeBinary,
cran_module.PropertyPlatform: platform,
cran_module.PropertyRVersion: rversion,
},
)
}
func uploadPackageFile(ctx *context.Context, compositeKey string, properties map[string]string) {
upload, close, err := ctx.UploadStream()
if err != nil {
apiError(ctx, http.StatusBadRequest, err)
return
}
if close {
defer upload.Close()
}
buf, err := packages_module.CreateHashedBufferFromReader(upload)
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
}
defer buf.Close()
pck, err := cran_module.ParsePackage(buf, buf.Size())
if err != nil {
if errors.Is(err, util.ErrInvalidArgument) {
apiError(ctx, http.StatusBadRequest, err)
} else {
apiError(ctx, http.StatusInternalServerError, err)
}
return
}
if _, err := buf.Seek(0, io.SeekStart); err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
}
_, _, err = packages_service.CreatePackageOrAddFileToExisting(
&packages_service.PackageCreationInfo{
PackageInfo: packages_service.PackageInfo{
Owner: ctx.Package.Owner,
PackageType: packages_model.TypeCran,
Name: pck.Name,
Version: pck.Version,
},
SemverCompatible: false,
Creator: ctx.Doer,
Metadata: pck.Metadata,
},
&packages_service.PackageFileCreationInfo{
PackageFileInfo: packages_service.PackageFileInfo{
Filename: fmt.Sprintf("%s_%s%s", pck.Name, pck.Version, pck.FileExtension),
CompositeKey: compositeKey,
},
Creator: ctx.Doer,
Data: buf,
IsLead: true,
Properties: properties,
},
)
if err != nil {
switch err {
case packages_model.ErrDuplicatePackageFile:
apiError(ctx, http.StatusConflict, err)
case packages_service.ErrQuotaTotalCount, packages_service.ErrQuotaTypeSize, packages_service.ErrQuotaTotalSize:
apiError(ctx, http.StatusForbidden, err)
default:
apiError(ctx, http.StatusInternalServerError, err)
}
return
}
ctx.Status(http.StatusCreated)
}
func DownloadSourcePackageFile(ctx *context.Context) {
downloadPackageFile(ctx, &cran_model.SearchOptions{
OwnerID: ctx.Package.Owner.ID,
FileType: cran_module.TypeSource,
Filename: ctx.Params("filename"),
})
}
func DownloadBinaryPackageFile(ctx *context.Context) {
downloadPackageFile(ctx, &cran_model.SearchOptions{
OwnerID: ctx.Package.Owner.ID,
FileType: cran_module.TypeBinary,
Platform: ctx.Params("platform"),
RVersion: ctx.Params("rversion"),
Filename: ctx.Params("filename"),
})
}
func downloadPackageFile(ctx *context.Context, opts *cran_model.SearchOptions) {
pf, err := cran_model.SearchFile(ctx, opts)
if err != nil {
if errors.Is(err, util.ErrNotExist) {
apiError(ctx, http.StatusNotFound, err)
} else {
apiError(ctx, http.StatusInternalServerError, err)
}
return
}
s, _, err := packages_service.GetPackageFileStream(ctx, pf)
if err != nil {
if errors.Is(err, util.ErrNotExist) {
apiError(ctx, http.StatusNotFound, err)
} else {
apiError(ctx, http.StatusInternalServerError, err)
}
return
}
defer s.Close()
ctx.ServeContent(s, &context.ServeHeaderOptions{
Filename: pf.Name,
LastModified: pf.CreatedUnix.AsLocalTime(),
})
}