2021-04-09 01:25:57 +03:00
// Copyright 2021 The Gitea Authors. All rights reserved.
2022-11-27 21:20:29 +03:00
// SPDX-License-Identifier: MIT
2021-04-09 01:25:57 +03:00
package lfs
import (
"bytes"
"context"
"errors"
"fmt"
"net/http"
"net/url"
"strings"
2021-07-24 19:03:58 +03:00
"code.gitea.io/gitea/modules/json"
2021-04-09 01:25:57 +03:00
"code.gitea.io/gitea/modules/log"
2021-08-18 16:10:39 +03:00
"code.gitea.io/gitea/modules/proxy"
2021-04-09 01:25:57 +03:00
)
2021-06-14 20:20:43 +03:00
const batchSize = 20
2021-04-09 01:25:57 +03:00
// HTTPClient is used to communicate with the LFS server
// https://github.com/git-lfs/git-lfs/blob/main/docs/api/batch.md
type HTTPClient struct {
client * http . Client
endpoint string
transfers map [ string ] TransferAdapter
}
2021-06-14 20:20:43 +03:00
// BatchSize returns the preferred size of batchs to process
func ( c * HTTPClient ) BatchSize ( ) int {
return batchSize
}
2021-11-20 12:34:05 +03:00
func newHTTPClient ( endpoint * url . URL , httpTransport * http . Transport ) * HTTPClient {
if httpTransport == nil {
httpTransport = & http . Transport {
Proxy : proxy . Proxy ( ) ,
}
}
2021-08-18 16:10:39 +03:00
hc := & http . Client {
2021-11-20 12:34:05 +03:00
Transport : httpTransport ,
2021-08-18 16:10:39 +03:00
}
2021-04-09 01:25:57 +03:00
client := & HTTPClient {
client : hc ,
endpoint : strings . TrimSuffix ( endpoint . String ( ) , "/" ) ,
transfers : make ( map [ string ] TransferAdapter ) ,
}
basic := & BasicTransferAdapter { hc }
client . transfers [ basic . Name ( ) ] = basic
return client
}
func ( c * HTTPClient ) transferNames ( ) [ ] string {
keys := make ( [ ] string , len ( c . transfers ) )
i := 0
for k := range c . transfers {
keys [ i ] = k
i ++
}
return keys
}
func ( c * HTTPClient ) batch ( ctx context . Context , operation string , objects [ ] Pointer ) ( * BatchResponse , error ) {
2021-06-14 20:20:43 +03:00
log . Trace ( "BATCH operation with objects: %v" , objects )
2021-04-09 01:25:57 +03:00
url := fmt . Sprintf ( "%s/objects/batch" , c . endpoint )
request := & BatchRequest { operation , c . transferNames ( ) , nil , objects }
payload := new ( bytes . Buffer )
2021-07-24 19:03:58 +03:00
err := json . NewEncoder ( payload ) . Encode ( request )
2021-04-09 01:25:57 +03:00
if err != nil {
2021-06-14 20:20:43 +03:00
log . Error ( "Error encoding json: %v" , err )
return nil , err
2021-04-09 01:25:57 +03:00
}
2021-06-14 20:20:43 +03:00
log . Trace ( "Calling: %s" , url )
2021-04-09 01:25:57 +03:00
req , err := http . NewRequestWithContext ( ctx , "POST" , url , payload )
if err != nil {
2021-06-14 20:20:43 +03:00
log . Error ( "Error creating request: %v" , err )
return nil , err
2021-04-09 01:25:57 +03:00
}
req . Header . Set ( "Content-type" , MediaType )
req . Header . Set ( "Accept" , MediaType )
res , err := c . client . Do ( req )
if err != nil {
select {
case <- ctx . Done ( ) :
return nil , ctx . Err ( )
default :
}
2021-06-14 20:20:43 +03:00
log . Error ( "Error while processing request: %v" , err )
return nil , err
2021-04-09 01:25:57 +03:00
}
defer res . Body . Close ( )
if res . StatusCode != http . StatusOK {
2021-06-14 20:20:43 +03:00
return nil , fmt . Errorf ( "Unexpected server response: %s" , res . Status )
2021-04-09 01:25:57 +03:00
}
var response BatchResponse
2021-07-24 19:03:58 +03:00
err = json . NewDecoder ( res . Body ) . Decode ( & response )
2021-04-09 01:25:57 +03:00
if err != nil {
2021-06-14 20:20:43 +03:00
log . Error ( "Error decoding json: %v" , err )
return nil , err
2021-04-09 01:25:57 +03:00
}
if len ( response . Transfer ) == 0 {
response . Transfer = "basic"
}
return & response , nil
}
// Download reads the specific LFS object from the LFS server
2021-06-14 20:20:43 +03:00
func ( c * HTTPClient ) Download ( ctx context . Context , objects [ ] Pointer , callback DownloadCallback ) error {
return c . performOperation ( ctx , objects , callback , nil )
}
// Upload sends the specific LFS object to the LFS server
func ( c * HTTPClient ) Upload ( ctx context . Context , objects [ ] Pointer , callback UploadCallback ) error {
return c . performOperation ( ctx , objects , nil , callback )
}
2021-04-09 01:25:57 +03:00
2021-06-14 20:20:43 +03:00
func ( c * HTTPClient ) performOperation ( ctx context . Context , objects [ ] Pointer , dc DownloadCallback , uc UploadCallback ) error {
if len ( objects ) == 0 {
return nil
}
operation := "download"
if uc != nil {
operation = "upload"
}
result , err := c . batch ( ctx , operation , objects )
2021-04-09 01:25:57 +03:00
if err != nil {
2021-06-14 20:20:43 +03:00
return err
2021-04-09 01:25:57 +03:00
}
transferAdapter , ok := c . transfers [ result . Transfer ]
if ! ok {
2021-06-14 20:20:43 +03:00
return fmt . Errorf ( "TransferAdapter not found: %s" , result . Transfer )
2021-04-09 01:25:57 +03:00
}
2021-06-14 20:20:43 +03:00
for _ , object := range result . Objects {
if object . Error != nil {
objectError := errors . New ( object . Error . Message )
log . Trace ( "Error on object %v: %v" , object . Pointer , objectError )
if uc != nil {
if _ , err := uc ( object . Pointer , objectError ) ; err != nil {
return err
}
} else {
if err := dc ( object . Pointer , nil , objectError ) ; err != nil {
return err
}
}
continue
}
2021-04-09 01:25:57 +03:00
2021-06-14 20:20:43 +03:00
if uc != nil {
if len ( object . Actions ) == 0 {
log . Trace ( "%v already present on server" , object . Pointer )
continue
}
link , ok := object . Actions [ "upload" ]
if ! ok {
log . Debug ( "%+v" , object )
return errors . New ( "Missing action 'upload'" )
}
content , err := uc ( object . Pointer , nil )
if err != nil {
return err
}
err = transferAdapter . Upload ( ctx , link , object . Pointer , content )
content . Close ( )
if err != nil {
return err
}
link , ok = object . Actions [ "verify" ]
if ok {
if err := transferAdapter . Verify ( ctx , link , object . Pointer ) ; err != nil {
return err
}
}
} else {
link , ok := object . Actions [ "download" ]
if ! ok {
log . Debug ( "%+v" , object )
return errors . New ( "Missing action 'download'" )
}
content , err := transferAdapter . Download ( ctx , link )
if err != nil {
return err
}
if err := dc ( object . Pointer , content , nil ) ; err != nil {
return err
}
}
2021-04-09 01:25:57 +03:00
}
2021-06-14 20:20:43 +03:00
return nil
2021-04-09 01:25:57 +03:00
}