feat: add file download and preview
This commit is contained in:
parent
fcb1689bb8
commit
d36dfaed44
12
api_file.go
12
api_file.go
@ -1,6 +1,9 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
"gosvc/httpserver"
|
"gosvc/httpserver"
|
||||||
"gosvc/logger"
|
"gosvc/logger"
|
||||||
"gosvc/validator"
|
"gosvc/validator"
|
||||||
@ -88,15 +91,20 @@ func (s *Service) HandleDownloadFile(
|
|||||||
resp *httpserver.Response,
|
resp *httpserver.Response,
|
||||||
) *httpserver.Response {
|
) *httpserver.Response {
|
||||||
path := req.QueryString("path")
|
path := req.QueryString("path")
|
||||||
|
preview := req.QueryBool("preview")
|
||||||
newPath := utils.NormalizePath(path)
|
newPath := utils.NormalizePath(path)
|
||||||
|
|
||||||
_, _, err := s.FileSystemManager.DownloadFile(req.Context(), utils.FullPath(newPath))
|
entry, downloader, err := s.FileSystemManager.DownloadFile(req.Context(), utils.FullPath(newPath))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("download %s: %v", path, err)
|
logger.Error("download %s: %v", path, err)
|
||||||
return resp.InternalServerError("download file failed, " + err.Error())
|
return resp.InternalServerError("download file failed, " + err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
return resp.NoContent()
|
if !preview {
|
||||||
|
resp.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s", entry.FullPath.Name()))
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp.ServeContent(entry.FullPath.Name(), time.Unix(entry.LastModificationTime, 0), downloader)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) HandleDeleteFile(
|
func (s *Service) HandleDeleteFile(
|
||||||
|
|||||||
@ -158,7 +158,7 @@ func (f *FileSystemManager) CreateFile(ctx context.Context, path utils.FullPath,
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *FileSystemManager) DownloadFile(ctx context.Context, path utils.FullPath) ([]byte, *utils.Entry, error) {
|
func (f *FileSystemManager) DownloadFile(ctx context.Context, path utils.FullPath) (*utils.Entry, io.ReadSeeker, error) {
|
||||||
f.RLock()
|
f.RLock()
|
||||||
defer f.RUnlock()
|
defer f.RUnlock()
|
||||||
|
|
||||||
@ -171,12 +171,12 @@ func (f *FileSystemManager) DownloadFile(ctx context.Context, path utils.FullPat
|
|||||||
return nil, nil, fmt.Errorf("cannot download directory")
|
return nil, nil, fmt.Errorf("cannot download directory")
|
||||||
}
|
}
|
||||||
|
|
||||||
content, err := f.storage.ReadObject(entry.S3Key)
|
downloader, err := f.storage.DownloadFile(entry.S3Key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, fmt.Errorf("read s3 object failed: %v", err)
|
return nil, nil, fmt.Errorf("read s3 object failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return content, entry, nil
|
return entry, downloader, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *FileSystemManager) DeleteFile(ctx context.Context, path utils.FullPath, isDir bool) error {
|
func (f *FileSystemManager) DeleteFile(ctx context.Context, path utils.FullPath, isDir bool) error {
|
||||||
|
|||||||
@ -4,7 +4,6 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
|
||||||
|
|
||||||
"robotfs/utils"
|
"robotfs/utils"
|
||||||
|
|
||||||
@ -122,19 +121,24 @@ func (sm *StorageManager) UploadFile(s3Key string, data io.Reader, contentType s
|
|||||||
return output, nil
|
return output, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sm *StorageManager) DownloadFile(S3Key string, localFilePath string) error {
|
func (sm *StorageManager) DownloadFile(S3Key string) (io.ReadSeeker, error) {
|
||||||
file, err := os.Create(localFilePath)
|
head, err := sm.s3Client.HeadObject(&s3.HeadObjectInput{
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer file.Close()
|
|
||||||
|
|
||||||
_, err = sm.s3Downloader.Download(file, &s3.GetObjectInput{
|
|
||||||
Bucket: aws.String(sm.bucketName),
|
Bucket: aws.String(sm.bucketName),
|
||||||
Key: aws.String(S3Key),
|
Key: aws.String(S3Key),
|
||||||
})
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
return err
|
result, err := sm.s3Client.GetObject(&s3.GetObjectInput{
|
||||||
|
Bucket: aws.String(sm.bucketName),
|
||||||
|
Key: aws.String(S3Key),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return utils.NewS3ReadSeeker(sm.s3Client, sm.bucketName, S3Key, result.Body, *head.ContentLength), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sm *StorageManager) ReadObject(S3Key string) ([]byte, error) {
|
func (sm *StorageManager) ReadObject(S3Key string) ([]byte, error) {
|
||||||
|
|||||||
@ -99,6 +99,11 @@ func (s *Service) RegisterRouteRules() {
|
|||||||
Method: http.MethodGet,
|
Method: http.MethodGet,
|
||||||
Handler: s.HandleDownloadFile,
|
Handler: s.HandleDownloadFile,
|
||||||
QueryRules: []*httpserver.QueryRule{
|
QueryRules: []*httpserver.QueryRule{
|
||||||
|
{
|
||||||
|
Key: "preview",
|
||||||
|
Type: httpserver.QueryTypeBool,
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Key: "path",
|
Key: "path",
|
||||||
Type: httpserver.QueryTypeString,
|
Type: httpserver.QueryTypeString,
|
||||||
|
|||||||
82
utils/s3ReadSeeker.go
Normal file
82
utils/s3ReadSeeker.go
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go/aws"
|
||||||
|
"github.com/aws/aws-sdk-go/service/s3"
|
||||||
|
)
|
||||||
|
|
||||||
|
type S3ReadSeeker struct {
|
||||||
|
client *s3.S3
|
||||||
|
bucket string
|
||||||
|
key string
|
||||||
|
offset int64
|
||||||
|
body io.ReadCloser
|
||||||
|
contentLen int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewS3ReadSeeker(client *s3.S3, bucket, key string, body io.ReadCloser, contentLen int64) *S3ReadSeeker {
|
||||||
|
return &S3ReadSeeker{
|
||||||
|
client: client,
|
||||||
|
bucket: bucket,
|
||||||
|
key: key,
|
||||||
|
body: body,
|
||||||
|
contentLen: contentLen,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *S3ReadSeeker) Read(p []byte) (n int, err error) {
|
||||||
|
if r.body == nil {
|
||||||
|
return 0, io.EOF
|
||||||
|
}
|
||||||
|
return r.body.Read(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *S3ReadSeeker) Seek(offset int64, whence int) (int64, error) {
|
||||||
|
if r.body != nil {
|
||||||
|
r.body.Close()
|
||||||
|
r.body = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var newOffset int64
|
||||||
|
switch whence {
|
||||||
|
case io.SeekStart:
|
||||||
|
newOffset = offset
|
||||||
|
case io.SeekCurrent:
|
||||||
|
newOffset = r.offset + offset
|
||||||
|
case io.SeekEnd:
|
||||||
|
newOffset = r.contentLen + offset
|
||||||
|
default:
|
||||||
|
return 0, fmt.Errorf("invalid whence")
|
||||||
|
}
|
||||||
|
|
||||||
|
if newOffset < 0 {
|
||||||
|
return 0, fmt.Errorf("negative offset")
|
||||||
|
}
|
||||||
|
|
||||||
|
if newOffset >= r.contentLen {
|
||||||
|
return r.contentLen, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := r.client.GetObject(&s3.GetObjectInput{
|
||||||
|
Bucket: aws.String(r.bucket),
|
||||||
|
Key: aws.String(r.key),
|
||||||
|
Range: aws.String(fmt.Sprintf("bytes=%d-", newOffset)),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
r.body = result.Body
|
||||||
|
r.offset = newOffset
|
||||||
|
return newOffset, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *S3ReadSeeker) Close() error {
|
||||||
|
if r.body != nil {
|
||||||
|
return r.body.Close()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user