mirror of
https://codeberg.org/Codeberg/pages-server.git
synced 2025-04-29 09:33:36 +02:00
175 lines
5.5 KiB
Go
175 lines
5.5 KiB
Go
package gitea
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"net/url"
|
|
"strings"
|
|
"time"
|
|
|
|
"code.gitea.io/sdk/gitea"
|
|
"github.com/rs/zerolog/log"
|
|
|
|
"codeberg.org/codeberg/pages/server/cache"
|
|
)
|
|
|
|
var ErrorNotFound = errors.New("not found")
|
|
|
|
const (
|
|
branchTimestampCacheKeyPrefix = "branchTime"
|
|
defaultBranchCacheKeyPrefix = "defaultBranch"
|
|
giteaObjectTypeHeader = "X-Gitea-Object-Type"
|
|
)
|
|
|
|
type Client struct {
|
|
sdkClient *gitea.Client
|
|
responseCache cache.SetGetKey
|
|
|
|
followSymlinks bool
|
|
supportLFS bool
|
|
}
|
|
|
|
func NewClient(giteaRoot, giteaAPIToken string, respCache cache.SetGetKey, followSymlinks, supportLFS bool) (*Client, error) {
|
|
rootURL, err := url.Parse(giteaRoot)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
giteaRoot = strings.Trim(rootURL.String(), "/")
|
|
|
|
stdClient := http.Client{Timeout: 10 * time.Second}
|
|
|
|
sdk, err := gitea.NewClient(giteaRoot, gitea.SetHTTPClient(&stdClient), gitea.SetToken(giteaAPIToken))
|
|
return &Client{
|
|
sdkClient: sdk,
|
|
responseCache: respCache,
|
|
followSymlinks: followSymlinks,
|
|
supportLFS: supportLFS,
|
|
}, err
|
|
}
|
|
|
|
func (client *Client) GiteaRawContent(targetOwner, targetRepo, ref, resource string) ([]byte, error) {
|
|
reader, _, err := client.ServeRawContent(targetOwner, targetRepo, ref, resource)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer reader.Close()
|
|
return io.ReadAll(reader)
|
|
}
|
|
|
|
func (client *Client) ServeRawContent(targetOwner, targetRepo, ref, resource string) (io.ReadCloser, *http.Response, error) {
|
|
// if cachedValue, ok := fileResponseCache.Get(uri + "?timestamp=" + o.timestamp()); ok && !cachedValue.(gitea.FileResponse).IsEmpty() {
|
|
// cachedResponse = cachedValue.(gitea.FileResponse)
|
|
reader, resp, err := client.sdkClient.GetFileReader(targetOwner, targetRepo, ref, resource, client.supportLFS)
|
|
if resp != nil {
|
|
switch resp.StatusCode {
|
|
case http.StatusOK:
|
|
|
|
// add caching
|
|
|
|
// Write the response body to the original request
|
|
// var cacheBodyWriter bytes.Buffer
|
|
// if res != nil {
|
|
// if res.Header.ContentLength() > fileCacheSizeLimit {
|
|
// // fasthttp else will set "Content-Length: 0"
|
|
// ctx.Response().SetBodyStream(&strings.Reader{}, -1)
|
|
//
|
|
// err = res.BodyWriteTo(ctx.Response.BodyWriter())
|
|
// } else {
|
|
// // TODO: cache is half-empty if request is cancelled - does the ctx.Err() below do the trick?
|
|
// err = res.BodyWriteTo(io.MultiWriter(ctx.Response().BodyWriter(), &cacheBodyWriter))
|
|
// }
|
|
// } else {
|
|
// _, err = ctx.Write(cachedResponse.Body)
|
|
// }
|
|
|
|
// if res != nil && res.Header.ContentLength() <= fileCacheSizeLimit && ctx.Err() == nil {
|
|
// cachedResponse.Exists = true
|
|
// cachedResponse.MimeType = mimeType
|
|
// cachedResponse.Body = cacheBodyWriter.Bytes()
|
|
// _ = fileResponseCache.Set(uri+"?timestamp="+o.timestamp(), cachedResponse, fileCacheTimeout)
|
|
// }
|
|
// store ETag in resp !!!!
|
|
|
|
objType := resp.Header.Get(giteaObjectTypeHeader)
|
|
log.Trace().Msgf("server raw content object: %s", objType)
|
|
if client.followSymlinks && objType == "symlink" {
|
|
// limit to 1000 chars
|
|
defer reader.Close()
|
|
linkDestBytes, err := io.ReadAll(io.LimitReader(reader, 10000))
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
linkDest := strings.TrimSpace(string(linkDestBytes))
|
|
|
|
log.Debug().Msgf("follow symlink from '%s' to '%s'", resource, linkDest)
|
|
return client.ServeRawContent(targetOwner, targetRepo, ref, linkDest)
|
|
}
|
|
|
|
return reader, resp.Response, err
|
|
case http.StatusNotFound:
|
|
|
|
// add not exist caching
|
|
// _ = fileResponseCache.Set(uri+"?timestamp="+o.timestamp(), gitea.FileResponse{
|
|
// Exists: false,
|
|
// }, fileCacheTimeout)
|
|
|
|
return nil, resp.Response, ErrorNotFound
|
|
default:
|
|
return nil, resp.Response, fmt.Errorf("unexpected status code '%d'", resp.StatusCode)
|
|
}
|
|
}
|
|
return nil, nil, err
|
|
}
|
|
|
|
func (client *Client) GiteaGetRepoBranchTimestamp(repoOwner, repoName, branchName string) (*BranchTimestamp, error) {
|
|
cacheKey := fmt.Sprintf("%s/%s/%s/%s", branchTimestampCacheKeyPrefix, repoOwner, repoName, branchName)
|
|
|
|
if stamp, ok := client.responseCache.Get(cacheKey); ok && stamp != nil {
|
|
return stamp.(*BranchTimestamp), nil
|
|
}
|
|
|
|
branch, resp, err := client.sdkClient.GetRepoBranch(repoOwner, repoName, branchName)
|
|
if err != nil {
|
|
if resp != nil && resp.StatusCode == http.StatusNotFound {
|
|
return &BranchTimestamp{}, ErrorNotFound
|
|
}
|
|
return &BranchTimestamp{}, err
|
|
}
|
|
if resp.StatusCode != http.StatusOK {
|
|
return &BranchTimestamp{}, fmt.Errorf("unexpected status code '%d'", resp.StatusCode)
|
|
}
|
|
|
|
stamp := &BranchTimestamp{
|
|
Branch: branch.Name,
|
|
Timestamp: branch.Commit.Timestamp,
|
|
}
|
|
|
|
if err := client.responseCache.Set(cacheKey, stamp, branchExistenceCacheTimeout); err != nil {
|
|
log.Error().Err(err).Msgf("error on store of repo branch timestamp [%s/%s@%s]", repoOwner, repoName, branchName)
|
|
}
|
|
return stamp, nil
|
|
}
|
|
|
|
func (client *Client) GiteaGetRepoDefaultBranch(repoOwner, repoName string) (string, error) {
|
|
cacheKey := fmt.Sprintf("%s/%s/%s", defaultBranchCacheKeyPrefix, repoOwner, repoName)
|
|
|
|
if branch, ok := client.responseCache.Get(cacheKey); ok && branch != nil {
|
|
return branch.(string), nil
|
|
}
|
|
|
|
repo, resp, err := client.sdkClient.GetRepo(repoOwner, repoName)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if resp.StatusCode != http.StatusOK {
|
|
return "", fmt.Errorf("unexpected status code '%d'", resp.StatusCode)
|
|
}
|
|
|
|
branch := repo.DefaultBranch
|
|
if err := client.responseCache.Set(cacheKey, branch, defaultBranchCacheTimeout); err != nil {
|
|
log.Error().Err(err).Msgf("error on store of repo default branch [%s/%s]", repoOwner, repoName)
|
|
}
|
|
return branch, nil
|
|
}
|