2020-04-05 09:20:50 +03:00
// Copyright 2020 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
2016-12-26 04:16:37 +03:00
package lfs
import (
"crypto/sha256"
"encoding/hex"
"errors"
2020-10-31 23:51:48 +03:00
"fmt"
2016-12-26 04:16:37 +03:00
"io"
"os"
2017-11-08 16:04:19 +03:00
"code.gitea.io/gitea/models"
2020-03-09 22:56:18 +03:00
"code.gitea.io/gitea/modules/log"
2020-09-08 18:45:10 +03:00
"code.gitea.io/gitea/modules/storage"
2016-12-26 04:16:37 +03:00
)
var (
errHashMismatch = errors . New ( "Content hash does not match OID" )
errSizeMismatch = errors . New ( "Content size does not match" )
)
2020-10-31 23:51:48 +03:00
// ErrRangeNotSatisfiable represents an error which request range is not satisfiable.
type ErrRangeNotSatisfiable struct {
FromByte int64
}
func ( err ErrRangeNotSatisfiable ) Error ( ) string {
return fmt . Sprintf ( "Requested range %d is not satisfiable" , err . FromByte )
}
// IsErrRangeNotSatisfiable returns true if the error is an ErrRangeNotSatisfiable
func IsErrRangeNotSatisfiable ( err error ) bool {
_ , ok := err . ( ErrRangeNotSatisfiable )
return ok
}
2016-12-26 04:16:37 +03:00
// ContentStore provides a simple file system based storage.
type ContentStore struct {
2020-09-08 18:45:10 +03:00
storage . ObjectStorage
2016-12-26 04:16:37 +03:00
}
2017-03-15 03:52:01 +03:00
// Get takes a Meta object and retrieves the content from the store, returning
2016-12-26 04:16:37 +03:00
// it as an io.Reader. If fromByte > 0, the reader starts from that byte
func ( s * ContentStore ) Get ( meta * models . LFSMetaObject , fromByte int64 ) ( io . ReadCloser , error ) {
2020-09-08 18:45:10 +03:00
f , err := s . Open ( meta . RelativePath ( ) )
2016-12-26 04:16:37 +03:00
if err != nil {
2020-09-08 18:45:10 +03:00
log . Error ( "Whilst trying to read LFS OID[%s]: Unable to open Error: %v" , meta . Oid , err )
2016-12-26 04:16:37 +03:00
return nil , err
}
if fromByte > 0 {
2020-10-31 23:51:48 +03:00
if fromByte >= meta . Size {
return nil , ErrRangeNotSatisfiable {
FromByte : fromByte ,
}
}
_ , err = f . Seek ( fromByte , io . SeekStart )
2020-03-09 22:56:18 +03:00
if err != nil {
log . Error ( "Whilst trying to read LFS OID[%s]: Unable to seek to %d Error: %v" , meta . Oid , fromByte , err )
}
2016-12-26 04:16:37 +03:00
}
return f , err
}
// Put takes a Meta object and an io.Reader and writes the content to the store.
func ( s * ContentStore ) Put ( meta * models . LFSMetaObject , r io . Reader ) error {
hash := sha256 . New ( )
2020-09-08 18:45:10 +03:00
rd := io . TeeReader ( r , hash )
p := meta . RelativePath ( )
written , err := s . Save ( p , rd )
2016-12-26 04:16:37 +03:00
if err != nil {
2020-09-08 18:45:10 +03:00
log . Error ( "Whilst putting LFS OID[%s]: Failed to copy to tmpPath: %s Error: %v" , meta . Oid , p , err )
2016-12-26 04:16:37 +03:00
return err
}
if written != meta . Size {
2020-09-08 18:45:10 +03:00
if err := s . Delete ( p ) ; err != nil {
log . Error ( "Cleaning the LFS OID[%s] failed: %v" , meta . Oid , err )
}
2016-12-26 04:16:37 +03:00
return errSizeMismatch
}
shaStr := hex . EncodeToString ( hash . Sum ( nil ) )
if shaStr != meta . Oid {
2020-09-08 18:45:10 +03:00
if err := s . Delete ( p ) ; err != nil {
log . Error ( "Cleaning the LFS OID[%s] failed: %v" , meta . Oid , err )
}
2016-12-26 04:16:37 +03:00
return errHashMismatch
}
2020-03-09 22:56:18 +03:00
return nil
2016-12-26 04:16:37 +03:00
}
// Exists returns true if the object exists in the content store.
2020-09-08 18:45:10 +03:00
func ( s * ContentStore ) Exists ( meta * models . LFSMetaObject ) ( bool , error ) {
_ , err := s . ObjectStorage . Stat ( meta . RelativePath ( ) )
if err != nil {
if os . IsNotExist ( err ) {
return false , nil
}
return false , err
2016-12-26 04:16:37 +03:00
}
2020-09-08 18:45:10 +03:00
return true , nil
2016-12-26 04:16:37 +03:00
}
2017-11-08 16:04:19 +03:00
// Verify returns true if the object exists in the content store and size is correct.
func ( s * ContentStore ) Verify ( meta * models . LFSMetaObject ) ( bool , error ) {
2020-09-08 18:45:10 +03:00
p := meta . RelativePath ( )
fi , err := s . ObjectStorage . Stat ( p )
if os . IsNotExist ( err ) || ( err == nil && fi . Size ( ) != meta . Size ) {
2017-11-08 16:04:19 +03:00
return false , nil
} else if err != nil {
2020-09-08 18:45:10 +03:00
log . Error ( "Unable stat file: %s for LFS OID[%s] Error: %v" , p , meta . Oid , err )
2017-11-08 16:04:19 +03:00
return false , err
}
return true , nil
}