store.go (4237B)
1 package main 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "fmt" 7 "io" 8 "os" 9 "path/filepath" 10 "sync" 11 12 "github.com/hashicorp/go-multierror" 13 14 "github.com/pkg/errors" 15 ) 16 17 // Gif images are stored as-is on disk with a corresponding json file that 18 // contains the metadata. Why did I use goroutines to parellelise writing just 19 // two files? Don't ask me that, man. 20 type gifdb struct { 21 Dir string 22 init sync.Once 23 } 24 25 // Lookup loads the rendered gif from disk. 26 func (db *gifdb) Lookup(key string) (*RenderedGif, bool, error) { 27 var ( 28 info os.FileInfo 29 err error 30 ) 31 db.init.Do(func() { 32 err = os.MkdirAll(db.Dir, 0755) 33 }) 34 if err != nil && !os.IsExist(err) { 35 return nil, false, errors.Wrap(err, "initialising") 36 } 37 meta := filepath.Join(db.Dir, key+".json") 38 info, err = os.Stat(meta) 39 if os.IsNotExist(err) { 40 return nil, false, nil 41 } 42 if err != nil { 43 return nil, false, err 44 } 45 if info.IsDir() { 46 return nil, false, fmt.Errorf("key leads to a directory, not a json file") 47 } 48 img := filepath.Join(db.Dir, key+".gif") 49 info, err = os.Stat(img) 50 if os.IsNotExist(err) { 51 return nil, false, nil 52 } 53 if err != nil { 54 return nil, false, err 55 } 56 if info.IsDir() { 57 return nil, false, fmt.Errorf("key leads to a directory, not a gif file") 58 } 59 var ( 60 failed = make(chan error) 61 done = make(chan interface{}) 62 wg = sync.WaitGroup{} 63 ) 64 wg.Add(2) 65 go func() { 66 defer wg.Done() 67 if name, err := func() (string, error) { 68 metaf, err := os.Open(meta) 69 if err != nil { 70 return "", errors.Wrap(err, "opening metadata file") 71 } 72 defer metaf.Close() 73 type metadata struct { 74 FileName string `json:"filename"` 75 } 76 var md metadata 77 if err := json.NewDecoder(metaf).Decode(&md); err != nil { 78 return "", errors.Wrap(err, "decoding metadata") 79 } 80 return md.FileName, nil 81 }(); err != nil { 82 failed <- err 83 } else { 84 done <- name 85 } 86 }() 87 go func() { 88 defer wg.Done() 89 if buf, err := func() (*bytes.Buffer, error) { 90 buf := bytes.NewBuffer(nil) 91 file, err := os.Open(img) 92 if err != nil { 93 return nil, errors.Wrap(err, "opening gif file") 94 } 95 defer file.Close() 96 if _, err := io.Copy(buf, file); err != nil { 97 return nil, errors.Wrap(err, "reading gif file") 98 } 99 return buf, nil 100 }(); err != nil { 101 failed <- err 102 } else { 103 done <- buf 104 } 105 }() 106 go func() { 107 wg.Wait() 108 close(failed) 109 }() 110 var failure error 111 go func() { 112 for err := range failed { 113 failure = multierror.Append(failure, err) 114 } 115 close(done) 116 }() 117 r := &RenderedGif{} 118 for v := range done { 119 switch v := v.(type) { 120 case string: 121 r.FileName = v 122 case *bytes.Buffer: 123 r.Data = v.Bytes() 124 } 125 } 126 if failure != nil { 127 return nil, false, failure 128 } 129 return r, true, nil 130 } 131 132 // Insert stores the rendered gif on disk. 133 func (db *gifdb) Insert(key string, img *RenderedGif) (err error) { 134 db.init.Do(func() { 135 err = os.MkdirAll(db.Dir, 0755) 136 }) 137 if err != nil && !os.IsExist(err) { 138 return errors.Wrap(err, "initialising") 139 } 140 var ( 141 failed = make(chan error) 142 wg = sync.WaitGroup{} 143 ) 144 wg.Add(2) 145 go func() { 146 defer wg.Done() 147 if err := func() error { 148 imgpath := filepath.Join(db.Dir, key+".gif") 149 imgf, err := os.Create(imgpath) 150 if err != nil { 151 return errors.Wrap(err, "creating gif file") 152 } 153 defer imgf.Close() 154 if _, err := io.Copy(imgf, bytes.NewReader(img.Data)); err != nil { 155 return errors.Wrap(err, "persisting gif to disk") 156 } 157 return nil 158 }(); err != nil { 159 failed <- err 160 } 161 }() 162 go func() { 163 defer wg.Done() 164 if err := func() error { 165 meta := filepath.Join(db.Dir, key+".json") 166 metaf, err := os.Create(meta) 167 if err != nil { 168 return errors.Wrap(err, "creating metadata file") 169 } 170 defer metaf.Close() 171 type metadata struct { 172 FileName string `json:"filename"` 173 } 174 if err := json.NewEncoder(metaf).Encode(metadata{ 175 FileName: img.FileName, 176 }); err != nil { 177 return errors.Wrap(err, "writing to metadata file") 178 } 179 return nil 180 }(); err != nil { 181 failed <- err 182 } 183 }() 184 go func() { 185 wg.Wait() 186 close(failed) 187 }() 188 for failure := range failed { 189 err = multierror.Append(err, failure) 190 } 191 return err 192 } 193 194 func (db *gifdb) Keys() (keys []string) { 195 // how do we figure out what keys are in the db? 196 return keys 197 }