github.com/astaxie/beego@v1.12.3/cache/file.go (about) 1 // Copyright 2014 beego Author. All Rights Reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package cache 16 17 import ( 18 "bytes" 19 "crypto/md5" 20 "encoding/gob" 21 "encoding/hex" 22 "encoding/json" 23 "fmt" 24 "io" 25 "io/ioutil" 26 "os" 27 "path/filepath" 28 "reflect" 29 "strconv" 30 "time" 31 ) 32 33 // FileCacheItem is basic unit of file cache adapter. 34 // it contains data and expire time. 35 type FileCacheItem struct { 36 Data interface{} 37 Lastaccess time.Time 38 Expired time.Time 39 } 40 41 // FileCache Config 42 var ( 43 FileCachePath = "cache" // cache directory 44 FileCacheFileSuffix = ".bin" // cache file suffix 45 FileCacheDirectoryLevel = 2 // cache file deep level if auto generated cache files. 46 FileCacheEmbedExpiry time.Duration // cache expire time, default is no expire forever. 47 ) 48 49 // FileCache is cache adapter for file storage. 50 type FileCache struct { 51 CachePath string 52 FileSuffix string 53 DirectoryLevel int 54 EmbedExpiry int 55 } 56 57 // NewFileCache Create new file cache with no config. 58 // the level and expiry need set in method StartAndGC as config string. 59 func NewFileCache() Cache { 60 // return &FileCache{CachePath:FileCachePath, FileSuffix:FileCacheFileSuffix} 61 return &FileCache{} 62 } 63 64 // StartAndGC will start and begin gc for file cache. 65 // the config need to be like {CachePath:"/cache","FileSuffix":".bin","DirectoryLevel":"2","EmbedExpiry":"0"} 66 func (fc *FileCache) StartAndGC(config string) error { 67 68 cfg := make(map[string]string) 69 err := json.Unmarshal([]byte(config), &cfg) 70 if err != nil { 71 return err 72 } 73 if _, ok := cfg["CachePath"]; !ok { 74 cfg["CachePath"] = FileCachePath 75 } 76 if _, ok := cfg["FileSuffix"]; !ok { 77 cfg["FileSuffix"] = FileCacheFileSuffix 78 } 79 if _, ok := cfg["DirectoryLevel"]; !ok { 80 cfg["DirectoryLevel"] = strconv.Itoa(FileCacheDirectoryLevel) 81 } 82 if _, ok := cfg["EmbedExpiry"]; !ok { 83 cfg["EmbedExpiry"] = strconv.FormatInt(int64(FileCacheEmbedExpiry.Seconds()), 10) 84 } 85 fc.CachePath = cfg["CachePath"] 86 fc.FileSuffix = cfg["FileSuffix"] 87 fc.DirectoryLevel, _ = strconv.Atoi(cfg["DirectoryLevel"]) 88 fc.EmbedExpiry, _ = strconv.Atoi(cfg["EmbedExpiry"]) 89 90 fc.Init() 91 return nil 92 } 93 94 // Init will make new dir for file cache if not exist. 95 func (fc *FileCache) Init() { 96 if ok, _ := exists(fc.CachePath); !ok { // todo : error handle 97 _ = os.MkdirAll(fc.CachePath, os.ModePerm) // todo : error handle 98 } 99 } 100 101 // get cached file name. it's md5 encoded. 102 func (fc *FileCache) getCacheFileName(key string) string { 103 m := md5.New() 104 io.WriteString(m, key) 105 keyMd5 := hex.EncodeToString(m.Sum(nil)) 106 cachePath := fc.CachePath 107 switch fc.DirectoryLevel { 108 case 2: 109 cachePath = filepath.Join(cachePath, keyMd5[0:2], keyMd5[2:4]) 110 case 1: 111 cachePath = filepath.Join(cachePath, keyMd5[0:2]) 112 } 113 114 if ok, _ := exists(cachePath); !ok { // todo : error handle 115 _ = os.MkdirAll(cachePath, os.ModePerm) // todo : error handle 116 } 117 118 return filepath.Join(cachePath, fmt.Sprintf("%s%s", keyMd5, fc.FileSuffix)) 119 } 120 121 // Get value from file cache. 122 // if non-exist or expired, return empty string. 123 func (fc *FileCache) Get(key string) interface{} { 124 fileData, err := FileGetContents(fc.getCacheFileName(key)) 125 if err != nil { 126 return "" 127 } 128 var to FileCacheItem 129 GobDecode(fileData, &to) 130 if to.Expired.Before(time.Now()) { 131 return "" 132 } 133 return to.Data 134 } 135 136 // GetMulti gets values from file cache. 137 // if non-exist or expired, return empty string. 138 func (fc *FileCache) GetMulti(keys []string) []interface{} { 139 var rc []interface{} 140 for _, key := range keys { 141 rc = append(rc, fc.Get(key)) 142 } 143 return rc 144 } 145 146 // Put value into file cache. 147 // timeout means how long to keep this file, unit of ms. 148 // if timeout equals fc.EmbedExpiry(default is 0), cache this item forever. 149 func (fc *FileCache) Put(key string, val interface{}, timeout time.Duration) error { 150 gob.Register(val) 151 152 item := FileCacheItem{Data: val} 153 if timeout == time.Duration(fc.EmbedExpiry) { 154 item.Expired = time.Now().Add((86400 * 365 * 10) * time.Second) // ten years 155 } else { 156 item.Expired = time.Now().Add(timeout) 157 } 158 item.Lastaccess = time.Now() 159 data, err := GobEncode(item) 160 if err != nil { 161 return err 162 } 163 return FilePutContents(fc.getCacheFileName(key), data) 164 } 165 166 // Delete file cache value. 167 func (fc *FileCache) Delete(key string) error { 168 filename := fc.getCacheFileName(key) 169 if ok, _ := exists(filename); ok { 170 return os.Remove(filename) 171 } 172 return nil 173 } 174 175 // Incr will increase cached int value. 176 // fc value is saving forever unless Delete. 177 func (fc *FileCache) Incr(key string) error { 178 data := fc.Get(key) 179 var incr int 180 if reflect.TypeOf(data).Name() != "int" { 181 incr = 0 182 } else { 183 incr = data.(int) + 1 184 } 185 fc.Put(key, incr, time.Duration(fc.EmbedExpiry)) 186 return nil 187 } 188 189 // Decr will decrease cached int value. 190 func (fc *FileCache) Decr(key string) error { 191 data := fc.Get(key) 192 var decr int 193 if reflect.TypeOf(data).Name() != "int" || data.(int)-1 <= 0 { 194 decr = 0 195 } else { 196 decr = data.(int) - 1 197 } 198 fc.Put(key, decr, time.Duration(fc.EmbedExpiry)) 199 return nil 200 } 201 202 // IsExist check value is exist. 203 func (fc *FileCache) IsExist(key string) bool { 204 ret, _ := exists(fc.getCacheFileName(key)) 205 return ret 206 } 207 208 // ClearAll will clean cached files. 209 // not implemented. 210 func (fc *FileCache) ClearAll() error { 211 return nil 212 } 213 214 // check file exist. 215 func exists(path string) (bool, error) { 216 _, err := os.Stat(path) 217 if err == nil { 218 return true, nil 219 } 220 if os.IsNotExist(err) { 221 return false, nil 222 } 223 return false, err 224 } 225 226 // FileGetContents Get bytes to file. 227 // if non-exist, create this file. 228 func FileGetContents(filename string) (data []byte, e error) { 229 return ioutil.ReadFile(filename) 230 } 231 232 // FilePutContents Put bytes to file. 233 // if non-exist, create this file. 234 func FilePutContents(filename string, content []byte) error { 235 return ioutil.WriteFile(filename, content, os.ModePerm) 236 } 237 238 // GobEncode Gob encodes file cache item. 239 func GobEncode(data interface{}) ([]byte, error) { 240 buf := bytes.NewBuffer(nil) 241 enc := gob.NewEncoder(buf) 242 err := enc.Encode(data) 243 if err != nil { 244 return nil, err 245 } 246 return buf.Bytes(), err 247 } 248 249 // GobDecode Gob decodes file cache item. 250 func GobDecode(data []byte, to *FileCacheItem) error { 251 buf := bytes.NewBuffer(data) 252 dec := gob.NewDecoder(buf) 253 return dec.Decode(&to) 254 } 255 256 func init() { 257 Register("file", NewFileCache) 258 }