binary.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
       ---
       binary.go (3941B)
       ---
            1 // Plain []byte cache.
            2 
            3 package binary
            4 
            5 import (
            6         "crypto/sha1"
            7         "errors"
            8         "io"
            9         "io/ioutil"
           10         "os"
           11         "path/filepath"
           12         "strings"
           13         "sync"
           14         "time"
           15         "unicode"
           16 )
           17 
           18 var ErrorInvalidCacheName = errors.New("none or invalid cache name")
           19 var ErrorNotAllowed = errors.New("not allowed")
           20 
           21 type CacheShard struct {
           22         //items map[string]interface{}
           23         lock *sync.RWMutex
           24 }
           25 
           26 type CacheShards map[int]*CacheShard
           27 
           28 type Cache struct {
           29         Dir string
           30 
           31         // NOTE: locking mechanism are per Cache instance.
           32         shards CacheShards
           33 }
           34 
           35 func New(dir string) *Cache {
           36         // prealloc shards, NOTE: hardcoded shard size, see also GetShard().
           37         shards := make(CacheShards, 256)
           38         for i := 0; i < 256; i++ {
           39                 shards[i] = &CacheShard{
           40                         //items: make(map[string]interface{}, 0),
           41                         lock: new(sync.RWMutex),
           42                 }
           43         }
           44         c := &Cache{
           45                 Dir:    dir,
           46                 shards: shards,
           47         }
           48         return c
           49 }
           50 
           51 func (c *Cache) GetShard(key string) (shard *CacheShard) {
           52         hasher := sha1.New()
           53         hasher.Write([]byte(key))
           54         h := hasher.Sum(nil)
           55         shardkey := int(h[0]) // first byte, so always 0-255.
           56         return c.shards[shardkey]
           57 }
           58 
           59 // returns (status, error).
           60 func (c *Cache) Get(name string, v *[]byte, timeout time.Duration) (bool, error) {
           61         name = sanitizename(name)
           62         if name == "" {
           63                 return false, ErrorInvalidCacheName
           64         }
           65 
           66         *v = nil // always clear.
           67 
           68         // locking mechanism (read).
           69         shard := c.GetShard(name)
           70         shard.lock.RLock()
           71         defer shard.lock.RUnlock()
           72 
           73         // NOTE: file doesn't exist: not an error in this case.
           74         file, err := os.Open(c.GetPath(name))
           75         if err != nil {
           76                 return false, nil
           77         }
           78         defer file.Close()
           79 
           80         fi, err := file.Stat()
           81         if err != nil {
           82                 return false, nil
           83         }
           84         // expired?
           85         now := time.Now()
           86         modtime := fi.ModTime().Add(timeout)
           87         if modtime.Before(now) || modtime.Equal(now) { // <=
           88                 return false, nil
           89         }
           90 
           91         // encode as object.
           92         data, err := ioutil.ReadAll(file)
           93         if err != nil {
           94                 return false, err
           95         }
           96         *v = data
           97         return true, nil
           98 }
           99 
          100 // Serve cache (or not) using custom function. Useful to use with
          101 // http.ServeFunc().
          102 // NOTE: unlike http.ServeContent this MUST be a file.
          103 func (c *Cache) ServeFunc(name string, fn func(io.ReadSeeker, time.Time) error, timeout time.Duration) (bool, error) {
          104         name = sanitizename(name)
          105         if name == "" {
          106                 return false, ErrorInvalidCacheName
          107         }
          108 
          109         // locking mechanism (read).
          110         shard := c.GetShard(name)
          111         shard.lock.RLock()
          112         defer shard.lock.RUnlock()
          113 
          114         // NOTE: file doesn't exist: not an error in this case.
          115         file, err := os.Open(c.GetPath(name))
          116         if err != nil {
          117                 return false, nil
          118         }
          119         defer file.Close()
          120 
          121         fi, err := file.Stat()
          122         if err != nil {
          123                 return false, nil
          124         }
          125         // not allowed, must be a file.
          126         if fi.IsDir() {
          127                 return false, ErrorNotAllowed
          128         }
          129         // expired?
          130         now := time.Now()
          131         modtime := fi.ModTime().Add(timeout)
          132         if modtime.Before(now) || modtime.Equal(now) { // <=
          133                 return false, nil
          134         }
          135         return true, fn(file, modtime)
          136 }
          137 
          138 func (c *Cache) Set(name string, v []byte) error {
          139         name = sanitizename(name)
          140         if name == "" {
          141                 return ErrorInvalidCacheName
          142         }
          143 
          144         // locking mechanism (RW).
          145         shard := c.GetShard(name)
          146         shard.lock.Lock()
          147         defer shard.lock.Unlock()
          148 
          149         err := ioutil.WriteFile(c.GetPath(name), v, 0644)
          150         if err != nil {
          151                 return err
          152         }
          153         return nil
          154 }
          155 
          156 // remove cached file.
          157 func (c *Cache) Invalidate(name string) error {
          158         name = sanitizename(name)
          159         if name == "" {
          160                 return ErrorInvalidCacheName
          161         }
          162 
          163         // locking mechanism (RW).
          164         shard := c.GetShard(name)
          165         shard.lock.Lock()
          166         defer shard.lock.Unlock()
          167 
          168         return os.Remove(c.GetPath(name))
          169 }
          170 
          171 // get path to filename (can be a relative path).
          172 func (c *Cache) GetPath(name string) string {
          173         return filepath.Join(c.Dir, sanitizename(name))
          174 }
          175 
          176 // same as regexp replace: [^a-zA-Z0-9_.-]*
          177 func sanitizename(name string) string {
          178         name = strings.Map(func(r rune) rune {
          179                 if !unicode.IsDigit(r) &&
          180                         !unicode.IsLetter(r) &&
          181                         !strings.ContainsRune("_.-", r) {
          182                         return -1 // drop
          183                 }
          184                 return r
          185         }, name)
          186         name = strings.Replace(name, "..", "", -1)
          187         if name == "." {
          188                 name = ""
          189         }
          190         return name
          191 }