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 }