giffer

Create .gif images from sites like youtube.com
Log | Files | Refs | LICENSE

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 }