serialize.go - randomcrap - random crap programs of varying quality
 (HTM) git clone git://git.codemadness.org/randomcrap
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) README
 (DIR) LICENSE
       ---
       serialize.go (3493B)
       ---
            1 // Serialize objects and store as a file to disk.
            2 // Supports:
            3 // - encoding/json
            4 // - encoding/gob
            5 // - encoding/xml
            6 
            7 package serialize
            8 
            9 import (
           10         "crypto/sha1"
           11         "errors"
           12         "io"
           13         "os"
           14         "path/filepath"
           15         "strings"
           16         "sync"
           17         "time"
           18         "unicode"
           19 )
           20 
           21 var ErrorInvalidCacheName = errors.New("none or invalid cache name")
           22 var ErrorNotAllowed = errors.New("not allowed")
           23 
           24 type Decoder interface {
           25         Decode(interface{}) error
           26 }
           27 
           28 type Encoder interface {
           29         Encode(interface{}) error
           30 }
           31 
           32 type DecoderFunc func(io.Reader) Decoder
           33 type EncoderFunc func(io.Writer) Encoder
           34 
           35 type CacheShard struct {
           36         //items map[string]interface{}
           37         lock *sync.RWMutex
           38 }
           39 
           40 type CacheShards map[int]*CacheShard
           41 
           42 type Cache struct {
           43         Dir string
           44 
           45         // NOTE: locking mechanism are per Cache instance.
           46         shards CacheShards
           47 
           48         NewDecoder DecoderFunc
           49         NewEncoder EncoderFunc
           50 }
           51 
           52 func New(dir string, dec DecoderFunc, enc EncoderFunc) *Cache {
           53         // prealloc shards, NOTE: hardcoded shard size, see also GetShard().
           54         shards := make(CacheShards, 256)
           55         for i := 0; i < 256; i++ {
           56                 shards[i] = &CacheShard{
           57                         //items: make(map[string]interface{}, 0),
           58                         lock: new(sync.RWMutex),
           59                 }
           60         }
           61         return &Cache{
           62                 Dir:        dir,
           63                 NewDecoder: dec,
           64                 NewEncoder: enc,
           65                 shards:     shards,
           66         }
           67 }
           68 
           69 func (c *Cache) GetShard(key string) (shard *CacheShard) {
           70         hasher := sha1.New()
           71         hasher.Write([]byte(key))
           72         h := hasher.Sum(nil)
           73         shardkey := int(h[0]) // first byte, so always 0-255.
           74         return c.shards[shardkey]
           75 }
           76 
           77 // returns (status, error).
           78 func (c *Cache) Get(name string, v interface{}, timeout time.Duration) (bool, error) {
           79         name = sanitizename(name)
           80         if name == "" {
           81                 return false, ErrorInvalidCacheName
           82         }
           83 
           84         // locking mechanism (read).
           85         shard := c.GetShard(name)
           86         shard.lock.RLock()
           87         defer shard.lock.RUnlock()
           88 
           89         // NOTE: file doesn't exist: not an error in this case.
           90         file, err := os.Open(c.GetPath(name))
           91         if err != nil {
           92                 return false, nil
           93         }
           94         defer file.Close()
           95 
           96         fi, err := file.Stat()
           97         if err != nil {
           98                 return false, nil
           99         }
          100         // expired?
          101         now := time.Now()
          102         modtime := fi.ModTime().Add(timeout)
          103         if modtime.Before(now) || modtime.Equal(now) { // <=
          104                 return false, nil
          105         }
          106 
          107         // encode as object.
          108         err = c.NewDecoder(file).Decode(v)
          109         if err != nil {
          110                 return false, err
          111         }
          112         return true, nil
          113 }
          114 
          115 func (c *Cache) Set(name string, v interface{}) error {
          116         name = sanitizename(name)
          117         if name == "" {
          118                 return ErrorInvalidCacheName
          119         }
          120 
          121         // locking mechanism (RW).
          122         shard := c.GetShard(name)
          123         shard.lock.Lock()
          124         defer shard.lock.Unlock()
          125 
          126         file, err := os.OpenFile(c.GetPath(name), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
          127         if err != nil {
          128                 return err
          129         }
          130         defer file.Close()
          131 
          132         err = c.NewEncoder(file).Encode(v)
          133         if err != nil {
          134                 return err
          135         }
          136         return nil
          137 }
          138 
          139 // remove cached file.
          140 func (c *Cache) Invalidate(name string) error {
          141         name = sanitizename(name)
          142         if name == "" {
          143                 return ErrorInvalidCacheName
          144         }
          145         // locking mechanism (RW).
          146         shard := c.GetShard(name)
          147         shard.lock.Lock()
          148         defer shard.lock.Unlock()
          149 
          150         return os.Remove(c.GetPath(name))
          151 }
          152 
          153 // get path to filename (can be a relative path).
          154 func (c *Cache) GetPath(name string) string {
          155         return filepath.Join(c.Dir, sanitizename(name))
          156 }
          157 
          158 // same as regexp replace: [^a-zA-Z0-9_.-]*
          159 func sanitizename(name string) string {
          160         name = strings.Map(func(r rune) rune {
          161                 if !unicode.IsDigit(r) &&
          162                         !unicode.IsLetter(r) &&
          163                         !strings.ContainsRune("_.-", r) {
          164                         return -1 // drop
          165                 }
          166                 return r
          167         }, name)
          168         name = strings.Replace(name, "..", "", -1)
          169         if name == "." {
          170                 name = ""
          171         }
          172         return name
          173 }