github.com/cozy/cozy-stack@v0.0.0-20240603063001-31110fa4cae1/pkg/previewfs/cache.go (about)

     1  package previewfs
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"encoding/hex"
     7  	"errors"
     8  	"fmt"
     9  	"io"
    10  	"os"
    11  	"path"
    12  	"strconv"
    13  	"time"
    14  
    15  	"github.com/cozy/cozy-stack/pkg/config/config"
    16  	"github.com/ncw/swift/v2"
    17  	"github.com/spf13/afero"
    18  )
    19  
    20  const (
    21  	containerName = "previews"
    22  	ttl           = 30 * 24 * time.Hour
    23  )
    24  
    25  // Cache is a interface for persisting icons & previews of PDF for later reuse.
    26  type Cache interface {
    27  	GetIcon(md5sum []byte) (*bytes.Buffer, error)
    28  	SetIcon(md5sum []byte, buffer *bytes.Buffer) error
    29  	GetPreview(md5sum []byte) (*bytes.Buffer, error)
    30  	SetPreview(md5sum []byte, buffer *bytes.Buffer) error
    31  }
    32  
    33  // SystemCache returns the global cache, using the configuration file.
    34  func SystemCache() Cache {
    35  	fsURL := config.FsURL()
    36  	switch fsURL.Scheme {
    37  	case config.SchemeFile, config.SchemeMem:
    38  		fs := afero.NewBasePathFs(afero.NewOsFs(), path.Join(fsURL.Path, containerName))
    39  		return aferoCache{fs}
    40  	case config.SchemeSwift, config.SchemeSwiftSecure:
    41  		conn := config.GetSwiftConnection()
    42  		ctx := context.Background()
    43  		return swiftCache{conn, ctx}
    44  	default:
    45  		panic(fmt.Errorf("previewfs: unknown storage provider %s", fsURL.Scheme))
    46  	}
    47  }
    48  
    49  type aferoCache struct {
    50  	fs afero.Fs
    51  }
    52  
    53  func (a aferoCache) GetIcon(md5sum []byte) (*bytes.Buffer, error) {
    54  	f, err := a.fs.Open(iconFilename(md5sum))
    55  	if err != nil {
    56  		return nil, err
    57  	}
    58  	return readClose(f)
    59  }
    60  
    61  func (a aferoCache) SetIcon(md5sum []byte, buffer *bytes.Buffer) error {
    62  	exists, err := afero.DirExists(a.fs, "/")
    63  	if err != nil || !exists {
    64  		_ = a.fs.MkdirAll("/", 0700)
    65  	}
    66  	f, err := a.fs.OpenFile(iconFilename(md5sum), os.O_CREATE|os.O_WRONLY, 0600)
    67  	if err != nil {
    68  		return err
    69  	}
    70  	return writeClose(f, buffer)
    71  }
    72  
    73  func (a aferoCache) GetPreview(md5sum []byte) (*bytes.Buffer, error) {
    74  	f, err := a.fs.Open(previewFilename(md5sum))
    75  	if err != nil {
    76  		return nil, err
    77  	}
    78  	return readClose(f)
    79  }
    80  
    81  func (a aferoCache) SetPreview(md5sum []byte, buffer *bytes.Buffer) error {
    82  	exists, err := afero.DirExists(a.fs, "/")
    83  	if err != nil || !exists {
    84  		_ = a.fs.MkdirAll("/", 0700)
    85  	}
    86  	f, err := a.fs.OpenFile(previewFilename(md5sum), os.O_CREATE|os.O_WRONLY, 0600)
    87  	if err != nil {
    88  		return err
    89  	}
    90  	return writeClose(f, buffer)
    91  }
    92  
    93  type swiftCache struct {
    94  	c   *swift.Connection
    95  	ctx context.Context
    96  }
    97  
    98  func (s swiftCache) GetIcon(md5sum []byte) (*bytes.Buffer, error) {
    99  	f, _, err := s.c.ObjectOpen(s.ctx, containerName, iconFilename(md5sum), false, nil)
   100  	if err != nil {
   101  		return nil, err
   102  	}
   103  	return readClose(f)
   104  }
   105  
   106  func (s swiftCache) SetIcon(md5sum []byte, buffer *bytes.Buffer) error {
   107  	objectName := iconFilename(md5sum)
   108  	objectMeta := swift.Metadata{"created-at": time.Now().Format(time.RFC3339)}
   109  	headers := objectMeta.ObjectHeaders()
   110  	headers["X-Delete-After"] = strconv.FormatInt(int64(ttl.Seconds()), 10)
   111  	f, err := s.c.ObjectCreate(s.ctx, containerName, objectName, true, "", "image/jpg", headers)
   112  	if err != nil {
   113  		return err
   114  	}
   115  	err = writeClose(f, buffer)
   116  	if errors.Is(err, swift.ContainerNotFound) || errors.Is(err, swift.ObjectNotFound) {
   117  		_ = s.c.ContainerCreate(s.ctx, containerName, nil)
   118  		f, err = s.c.ObjectCreate(s.ctx, containerName, objectName, true, "", "image/jpg", headers)
   119  		if err == nil {
   120  			err = writeClose(f, buffer)
   121  		}
   122  	}
   123  	return err
   124  }
   125  
   126  func (s swiftCache) GetPreview(md5sum []byte) (*bytes.Buffer, error) {
   127  	f, _, err := s.c.ObjectOpen(s.ctx, containerName, previewFilename(md5sum), false, nil)
   128  	if err != nil {
   129  		return nil, err
   130  	}
   131  	return readClose(f)
   132  }
   133  
   134  func (s swiftCache) SetPreview(md5sum []byte, buffer *bytes.Buffer) error {
   135  	objectName := previewFilename(md5sum)
   136  	objectMeta := swift.Metadata{"created-at": time.Now().Format(time.RFC3339)}
   137  	headers := objectMeta.ObjectHeaders()
   138  	headers["X-Delete-After"] = strconv.FormatInt(int64(ttl.Seconds()), 10)
   139  	f, err := s.c.ObjectCreate(s.ctx, containerName, objectName, true, "", "image/jpg", headers)
   140  	if err != nil {
   141  		return err
   142  	}
   143  	err = writeClose(f, buffer)
   144  	if errors.Is(err, swift.ContainerNotFound) || errors.Is(err, swift.ObjectNotFound) {
   145  		_ = s.c.ContainerCreate(s.ctx, containerName, nil)
   146  		f, err = s.c.ObjectCreate(s.ctx, containerName, objectName, true, "", "image/jpg", headers)
   147  		if err == nil {
   148  			err = writeClose(f, buffer)
   149  		}
   150  	}
   151  	return err
   152  }
   153  
   154  func iconFilename(md5sum []byte) string {
   155  	return "icon-" + hex.EncodeToString(md5sum) + ".jpg"
   156  }
   157  
   158  func previewFilename(md5sum []byte) string {
   159  	return hex.EncodeToString(md5sum) + ".jpg"
   160  }
   161  
   162  func readClose(f io.ReadCloser) (*bytes.Buffer, error) {
   163  	buffer := &bytes.Buffer{}
   164  	_, err := buffer.ReadFrom(f)
   165  	if errc := f.Close(); errc != nil && err == nil {
   166  		return nil, errc
   167  	}
   168  	return buffer, err
   169  }
   170  
   171  func writeClose(f io.WriteCloser, buffer *bytes.Buffer) error {
   172  	_, err := f.Write(buffer.Bytes())
   173  	if errc := f.Close(); errc != nil && err == nil {
   174  		err = errc
   175  	}
   176  	return err
   177  }