3
0
Files
corteza/compose/automation/attachment_handler.go
2022-09-20 12:34:28 +02:00

228 lines
4.9 KiB
Go

package automation
import (
"bufio"
"bytes"
"context"
"fmt"
"io"
"io/ioutil"
"os"
"strings"
"github.com/cortezaproject/corteza-server/compose/types"
)
type (
attachmentService interface {
FindByID(ctx context.Context, namespaceID, attachmentID uint64) (*types.Attachment, error)
CreateRecordAttachment(ctx context.Context, namespaceID uint64, name string, size int64, fh io.ReadSeeker, moduleID, recordID uint64, fieldName string) (att *types.Attachment, err error)
DeleteByID(ctx context.Context, namespaceID uint64, attachmentID uint64) error
OpenOriginal(att *types.Attachment) (io.ReadSeekCloser, error)
OpenPreview(att *types.Attachment) (io.ReadSeekCloser, error)
}
attachmentHandler struct {
reg modulesHandlerRegistry
svc attachmentService
}
attachmentLookup interface {
GetAttachment() (bool, uint64, *types.Attachment)
}
)
func AttachmentHandler(reg modulesHandlerRegistry, svc attachmentService) *attachmentHandler {
h := &attachmentHandler{
reg: reg,
svc: svc,
}
h.register()
return h
}
func (h attachmentHandler) lookup(ctx context.Context, args *attachmentLookupArgs) (results *attachmentLookupResults, err error) {
results = &attachmentLookupResults{}
results.Attachment, err = h.svc.FindByID(ctx, 0, args.Attachment)
return
}
func (h attachmentHandler) delete(ctx context.Context, args *attachmentDeleteArgs) error {
return h.svc.DeleteByID(ctx, 0, args.Attachment)
}
func (h attachmentHandler) openOriginal(ctx context.Context, args *attachmentOpenOriginalArgs) (*attachmentOpenOriginalResults, error) {
att, err := lookupAttachment(ctx, h.svc, args)
if err != nil {
return nil, err
}
r := &attachmentOpenOriginalResults{}
r.Content, err = h.svc.OpenOriginal(att)
if err != nil {
return nil, err
}
// @todo we need to call Close() when file is read (or at the end of the workflow)
// some kind of workflow-cleanup facility is needed
return r, nil
}
func (h attachmentHandler) openPreview(ctx context.Context, args *attachmentOpenPreviewArgs) (*attachmentOpenPreviewResults, error) {
att, err := lookupAttachment(ctx, h.svc, args)
if err != nil {
return nil, err
}
r := &attachmentOpenPreviewResults{}
r.Content, err = h.svc.OpenOriginal(att)
if err != nil {
return nil, err
}
// @todo we need to call Close() when file is read (or at the end of the workflow)
// some kind of workflow-cleanup facility is needed
return r, nil
}
func (h attachmentHandler) create(ctx context.Context, args *attachmentCreateArgs) (*attachmentCreateResults, error) {
var (
err error
att *types.Attachment
fh io.ReadSeeker
size int64
)
switch {
case len(args.contentBytes) > 0:
size = int64(len(args.contentBytes))
fh = bytes.NewReader(args.contentBytes)
case args.contentStream != nil:
if rs, is := args.contentStream.(io.ReadSeeker); is {
_, err = rs.Seek(0, 0)
if err != nil {
return nil, err
}
size, err = getReaderSize(rs)
if err != nil {
return nil, err
}
_, err = rs.Seek(0, 0)
if err != nil {
return nil, err
}
fh = rs
} else {
// In case we only got a reader...
//
// For future proofing, for handling larger attachment, we create a temp.
// file which we then use as a reader
// Preparations
tmpf, err := ioutil.TempFile("", "reader")
if err != nil {
return nil, err
}
defer tmpf.Close()
defer os.Remove(tmpf.Name())
// Writing content to file
w := bufio.NewWriter(tmpf)
r := args.contentStream
buf := make([]byte, 1024)
for {
// read
n, err := r.Read(buf)
if err != nil && err != io.EOF {
return nil, err
}
if n == 0 {
break
}
// on-the-fly size calculation
size += int64(n)
// write
if _, err := w.Write(buf[:n]); err != nil {
return nil, err
}
}
if err = w.Flush(); err != nil {
return nil, err
}
_, err = tmpf.Seek(0, 0)
if err != nil {
return nil, err
}
fh = tmpf
}
default:
fh = strings.NewReader(args.contentString)
size = int64(len(args.contentString))
}
switch {
case args.Resource != nil:
att, err = h.svc.CreateRecordAttachment(
ctx,
args.Resource.NamespaceID,
args.Name,
size,
fh,
args.Resource.ModuleID,
args.Resource.ID,
args.FieldName,
)
default:
return nil, fmt.Errorf("unknown resource")
}
if err != nil {
return nil, err
}
return &attachmentCreateResults{att}, nil
}
func lookupAttachment(ctx context.Context, svc attachmentService, args attachmentLookup) (*types.Attachment, error) {
_, ID, attachment := args.GetAttachment()
switch {
case attachment != nil:
return attachment, nil
case ID > 0:
return svc.FindByID(ctx, 0, ID)
}
return nil, fmt.Errorf("empty attachment lookup params")
}
func getReaderSize(r io.Reader) (size int64, err error) {
buf := make([]byte, 1024)
var n int
for {
// read a chunk
n, err = r.Read(buf)
if err != nil && err != io.EOF {
return 0, err
}
if n == 0 {
break
}
size += int64(n)
}
return size, nil
}