github.com/bhojpur/cache@v0.0.4/pkg/file/cache.go (about) 1 package file 2 3 // Copyright (c) 2018 Bhojpur Consulting Private Limited, India. All rights reserved. 4 5 // Permission is hereby granted, free of charge, to any person obtaining a copy 6 // of this software and associated documentation files (the "Software"), to deal 7 // in the Software without restriction, including without limitation the rights 8 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 // copies of the Software, and to permit persons to whom the Software is 10 // furnished to do so, subject to the following conditions: 11 12 // The above copyright notice and this permission notice shall be included in 13 // all copies or substantial portions of the Software. 14 15 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 // THE SOFTWARE. 22 23 import ( 24 "encoding/binary" 25 "errors" 26 "os/user" 27 "strings" 28 "sync" 29 "time" 30 31 memcache "github.com/bhojpur/cache/pkg/memory" 32 "github.com/mattn/go-nulltype" 33 ) 34 35 var ( 36 NullString = nulltype.NullString{} 37 ) 38 39 type KV struct { 40 Key string 41 Val string 42 TTL time.Duration 43 } 44 45 type FileCache interface { 46 GetBytes(key string) ([]byte, error) 47 Get(key string) (nulltype.NullString, error) 48 SetBytes(key string, val []byte, ttl time.Duration) error 49 Set(key, val string, ttl time.Duration) error 50 TTL(key string) (time.Duration, error) 51 Expire(key string, ttl time.Duration) error 52 Del(key string) error 53 Range() ([]*KV, error) 54 } 55 56 func New(filepath string) FileCache { 57 if strings.HasPrefix(filepath, "~") { 58 u, err := user.Current() 59 if err != nil { 60 panic(err) 61 } 62 filepath = u.HomeDir + filepath[1:] 63 } 64 return &CacheImpl{ 65 filepath: filepath, 66 bucket: []byte("filer"), 67 } 68 } 69 70 type CacheImpl struct { 71 filepath string 72 bucket []byte 73 bOnce sync.Once 74 conn *memcache.DB 75 } 76 77 func (r *CacheImpl) GetBytes(key string) ([]byte, error) { 78 ttl, result, err := r.getWithExpire(key) 79 if err != nil { 80 return nil, err 81 } else if ttl < 0 { 82 return nil, nil 83 } 84 return result, nil 85 } 86 87 func (r *CacheImpl) Get(key string) (nulltype.NullString, error) { 88 ttl, result, err := r.getWithExpire(key) 89 //fmt.Println(ttl, result, err) 90 if err != nil { 91 return NullString, err 92 } else if ttl < 0 { 93 return nulltype.NullString{}, nil 94 } 95 return nulltype.NullStringOf(string(result)), nil 96 } 97 98 func (r *CacheImpl) SetBytes(key string, val []byte, ttl time.Duration) error { 99 if err := r.newConn(); err != nil { 100 return err 101 } 102 103 return r.conn.Update(func(tx *memcache.Tx) error { 104 b, err := tx.CreateBucketIfNotExists(r.bucket) 105 if err != nil { 106 return err 107 } 108 109 buf := make([]byte, 8+len(val)) 110 binary.PutVarint(buf[:8], toMillisecond(ttl)) 111 copy(buf[8:], val) 112 113 //fmt.Println(key, buf[:8], buf[8:]) 114 return b.Put([]byte(key), buf) 115 }) 116 } 117 118 func (r *CacheImpl) Set(key, val string, ttl time.Duration) error { 119 if err := r.newConn(); err != nil { 120 return err 121 } 122 123 return r.conn.Update(func(tx *memcache.Tx) error { 124 b, err := tx.CreateBucketIfNotExists(r.bucket) 125 if err != nil { 126 return err 127 } 128 129 buf := make([]byte, 8+len(val)) 130 binary.PutVarint(buf[:8], toMillisecond(ttl)) 131 copy(buf[8:], val) 132 133 //fmt.Println(key, buf[:8], buf[8:]) 134 return b.Put([]byte(key), buf) 135 }) 136 } 137 138 func (r *CacheImpl) TTL(key string) (time.Duration, error) { 139 ttl, _, err := r.getWithExpire(key) 140 if err != nil { 141 return -1, err 142 } else if ttl < -1 { 143 return -1, nil 144 } 145 return time.Duration(ttl) * time.Millisecond, nil 146 } 147 148 func (r *CacheImpl) Expire(key string, ttl time.Duration) error { 149 _, result, err := r.getWithExpire(key) 150 if err != nil { 151 return err 152 } else if ttl < -1 { 153 return ErrKeyExpired 154 } 155 return r.Set(key, string(result), ttl) 156 } 157 158 func (r *CacheImpl) Del(key string) error { 159 if err := r.newConn(); err != nil { 160 return err 161 } 162 163 return r.conn.Update(func(tx *memcache.Tx) error { 164 b, err := tx.CreateBucketIfNotExists(r.bucket) 165 if err != nil { 166 return err 167 } 168 return b.Delete([]byte(key)) 169 }) 170 } 171 172 func (r *CacheImpl) Range() ([]*KV, error) { 173 if err := r.newConn(); err != nil { 174 return nil, err 175 } 176 177 var kvs []*KV 178 if err := r.conn.View(func(tx *memcache.Tx) error { 179 b := tx.Bucket(r.bucket) 180 if b == nil { 181 return nil 182 } 183 return b.ForEach(func(k, v []byte) error { 184 expiredAt, err := binaryInt(v[:8]) 185 if err != nil { 186 return err 187 } 188 ttl := expiredAt - int(time.Now().UnixNano()/int64(1000000)) 189 if ttl < 0 { 190 // TODO: 删除 191 return nil 192 } 193 194 kvs = append(kvs, &KV{ 195 Key: string(k), 196 Val: string(v[8:]), 197 TTL: time.Duration(ttl) * time.Millisecond, 198 }) 199 return nil 200 }) 201 }); err != nil { 202 return nil, err 203 } 204 return kvs, nil 205 } 206 207 func (r *CacheImpl) newConn() error { 208 if r.conn == nil { 209 db, err := memcache.Open(r.filepath, 0600, nil) 210 if err != nil { 211 return err 212 } 213 r.conn = db 214 } 215 return nil 216 } 217 218 func (r *CacheImpl) getOriginData(key string) ([]byte, error) { 219 if err := r.newConn(); err != nil { 220 return nil, err 221 } 222 223 var result []byte 224 if err := r.conn.View(func(tx *memcache.Tx) error { 225 b := tx.Bucket(r.bucket) 226 if b == nil { 227 return nil 228 } 229 230 result = b.Get([]byte(key)) 231 return nil 232 }); err != nil { 233 return nil, err 234 } 235 236 return result, nil 237 } 238 239 func (r *CacheImpl) getWithExpire(key string) (int, []byte, error) { 240 result, err := r.getOriginData(key) 241 //fmt.Println(1, result, err) 242 if err != nil { 243 return -1, nil, nil 244 } else if result == nil { 245 return -1, nil, nil 246 } 247 expiredAt, err := binaryInt(result[:8]) 248 if err != nil { 249 return -1, nil, err 250 } 251 ttl := expiredAt - int(time.Now().UnixNano()/int64(1000000)) 252 if ttl < 0 { 253 // 过期了 254 // TODO: 删除 255 return -1, nil, err 256 } 257 258 return ttl, result[8:], nil 259 } 260 261 func toMillisecond(ttl time.Duration) int64 { 262 return int64(time.Now().Add(ttl).UnixNano() / int64(1000000)) 263 } 264 265 func binaryInt(buf []byte) (int, error) { 266 x, n := binary.Varint(buf) 267 if n == 0 { 268 return 0, errors.New("buf too small") 269 } else if n < 0 { 270 return 0, errors.New("value larger than 64 bits (overflow) and -n is the number of bytes read") 271 } 272 273 return int(x), nil 274 }