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  }