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 }