github.com/cozy/cozy-stack@v0.0.0-20240327093429-939e4a21320e/model/vfs/vfsafero/thumbs.go (about)

     1  package vfsafero
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"net/http"
     7  	"os"
     8  	"path"
     9  
    10  	"github.com/cozy/cozy-stack/model/vfs"
    11  	"github.com/cozy/cozy-stack/pkg/consts"
    12  	multierror "github.com/hashicorp/go-multierror"
    13  	"github.com/spf13/afero"
    14  )
    15  
    16  // NewThumbsFs creates a new thumb filesystem base on a afero.Fs.
    17  func NewThumbsFs(fs afero.Fs) vfs.Thumbser {
    18  	return &thumbs{fs}
    19  }
    20  
    21  type thumbs struct {
    22  	fs afero.Fs
    23  }
    24  
    25  type thumb struct {
    26  	afero.File
    27  	fs      afero.Fs
    28  	tmpname string
    29  	newname string
    30  }
    31  
    32  func (t *thumb) Abort() error {
    33  	// Create an empty file that indicates that the thumbnail generation has failed
    34  	if f, err := t.fs.Create(t.newname); err == nil {
    35  		_ = f.Close()
    36  	}
    37  	return t.fs.Remove(t.tmpname)
    38  }
    39  
    40  func (t *thumb) Commit() error {
    41  	return t.fs.Rename(t.tmpname, t.newname)
    42  }
    43  
    44  func (t *thumbs) CreateThumb(img *vfs.FileDoc, format string) (vfs.ThumbFiler, error) {
    45  	newname := t.makeName(img.ID(), format)
    46  	dir := path.Dir(newname)
    47  	if base := dir; base != "." {
    48  		if err := t.fs.MkdirAll(dir, 0755); err != nil {
    49  			return nil, err
    50  		}
    51  	}
    52  	f, err := afero.TempFile(t.fs, dir, "cozy-thumb")
    53  	if err != nil {
    54  		return nil, err
    55  	}
    56  	tmpname := f.Name()
    57  	th := &thumb{
    58  		File:    f,
    59  		fs:      t.fs,
    60  		tmpname: tmpname,
    61  		newname: newname,
    62  	}
    63  	return th, nil
    64  }
    65  
    66  func (t *thumbs) RemoveThumbs(img *vfs.FileDoc, formats []string) error {
    67  	var errm error
    68  	for _, format := range formats {
    69  		if err := t.fs.Remove(t.makeName(img.ID(), format)); err != nil && !os.IsNotExist(err) {
    70  			errm = multierror.Append(errm, err)
    71  		}
    72  	}
    73  	return errm
    74  }
    75  
    76  func (t *thumbs) ThumbExists(img *vfs.FileDoc, format string) (bool, error) {
    77  	name := t.makeName(img.ID(), format)
    78  	infos, err := t.fs.Stat(name)
    79  	if os.IsNotExist(err) {
    80  		return false, nil
    81  	}
    82  	if err != nil {
    83  		return false, err
    84  	}
    85  	return infos.Size() > 0, nil
    86  }
    87  
    88  func (t *thumbs) ServeThumbContent(w http.ResponseWriter, req *http.Request,
    89  	img *vfs.FileDoc, format string) error {
    90  	name := t.makeName(img.ID(), format)
    91  	s, err := t.fs.Stat(name)
    92  	if err != nil {
    93  		return err
    94  	}
    95  	if s.Size() == 0 {
    96  		return os.ErrInvalid
    97  	}
    98  	f, err := t.fs.Open(name)
    99  	if err != nil {
   100  		return err
   101  	}
   102  	defer f.Close()
   103  	http.ServeContent(w, req, name, s.ModTime(), f)
   104  	return nil
   105  }
   106  
   107  func (t *thumbs) CreateNoteThumb(id, mime, format string) (vfs.ThumbFiler, error) {
   108  	newname := t.makeName(id, format)
   109  	dir := path.Dir(newname)
   110  	if base := dir; base != "." {
   111  		if err := t.fs.MkdirAll(dir, 0755); err != nil {
   112  			return nil, err
   113  		}
   114  	}
   115  	f, err := afero.TempFile(t.fs, dir, "note-thumb")
   116  	if err != nil {
   117  		return nil, err
   118  	}
   119  	tmpname := f.Name()
   120  	th := &thumb{
   121  		File:    f,
   122  		fs:      t.fs,
   123  		tmpname: tmpname,
   124  		newname: newname,
   125  	}
   126  	return th, nil
   127  }
   128  
   129  func (t *thumbs) OpenNoteThumb(id, format string) (io.ReadCloser, error) {
   130  	name := t.makeName(id, format)
   131  	return t.fs.Open(name)
   132  }
   133  
   134  func (t *thumbs) RemoveNoteThumb(id string, formats []string) error {
   135  	var errm error
   136  	for _, format := range formats {
   137  		err := t.fs.Remove(t.makeName(id, format))
   138  		if err != nil && !os.IsNotExist(err) {
   139  			errm = multierror.Append(errm, err)
   140  		}
   141  	}
   142  	return errm
   143  }
   144  
   145  func (t *thumbs) ServeNoteThumbContent(w http.ResponseWriter, req *http.Request, id string) error {
   146  	name := t.makeName(id, consts.NoteImageThumbFormat)
   147  	s, err := t.fs.Stat(name)
   148  	if err != nil {
   149  		name = t.makeName(id, consts.NoteImageOriginalFormat)
   150  		s, err = t.fs.Stat(name)
   151  		if err != nil {
   152  			return err
   153  		}
   154  	}
   155  	f, err := t.fs.Open(name)
   156  	if err != nil {
   157  		return err
   158  	}
   159  	defer f.Close()
   160  	http.ServeContent(w, req, name, s.ModTime(), f)
   161  	return nil
   162  }
   163  
   164  func (t *thumbs) makeName(imgID string, format string) string {
   165  	dir := imgID[:4]
   166  	ext := ".jpg"
   167  	if format == consts.NoteImageOriginalFormat {
   168  		ext = ""
   169  	}
   170  	name := fmt.Sprintf("%s-%s%s", imgID, format, ext)
   171  	return path.Join("/", dir, name)
   172  }