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 }