// Beego (http://beego.me/) // @description beego is an open-source, high-performance web framework for the Go programming language. // @link http://github.com/astaxie/beego for the canonical source repository // @license http://github.com/astaxie/beego/blob/master/LICENSE // @authors astaxie package cache import ( "bytes" "crypto/md5" "encoding/gob" "encoding/hex" "encoding/json" "fmt" "io" "os" "path/filepath" "reflect" "strconv" "time" ) func init() { Register("file", NewFileCache()) } // FileCacheItem is basic unit of file cache adapter. // it contains data and expire time. type FileCacheItem struct { Data interface{} Lastaccess int64 Expired int64 } var ( FileCachePath string = "cache" // cache directory FileCacheFileSuffix string = ".bin" // cache file suffix FileCacheDirectoryLevel int = 2 // cache file deep level if auto generated cache files. FileCacheEmbedExpiry int64 = 0 // cache expire time, default is no expire forever. ) // FileCache is cache adapter for file storage. type FileCache struct { CachePath string FileSuffix string DirectoryLevel int EmbedExpiry int } // Create new file cache with no config. // the level and expiry need set in method StartAndGC as config string. func NewFileCache() *FileCache { // return &FileCache{CachePath:FileCachePath, FileSuffix:FileCacheFileSuffix} return &FileCache{} } // Start and begin gc for file cache. // the config need to be like {CachePath:"/cache","FileSuffix":".bin","DirectoryLevel":2,"EmbedExpiry":0} func (this *FileCache) StartAndGC(config string) error { var cfg map[string]string json.Unmarshal([]byte(config), &cfg) //fmt.Println(cfg) //fmt.Println(config) if _, ok := cfg["CachePath"]; !ok { cfg["CachePath"] = FileCachePath } if _, ok := cfg["FileSuffix"]; !ok { cfg["FileSuffix"] = FileCacheFileSuffix } if _, ok := cfg["DirectoryLevel"]; !ok { cfg["DirectoryLevel"] = strconv.Itoa(FileCacheDirectoryLevel) } if _, ok := cfg["EmbedExpiry"]; !ok { cfg["EmbedExpiry"] = strconv.FormatInt(FileCacheEmbedExpiry, 10) } this.CachePath = cfg["CachePath"] this.FileSuffix = cfg["FileSuffix"] this.DirectoryLevel, _ = strconv.Atoi(cfg["DirectoryLevel"]) this.EmbedExpiry, _ = strconv.Atoi(cfg["EmbedExpiry"]) this.Init() return nil } // Init will make new dir for file cache if not exist. func (this *FileCache) Init() { app := filepath.Dir(os.Args[0]) this.CachePath = filepath.Join(app, this.CachePath) ok, err := exists(this.CachePath) if err != nil { // print error //fmt.Println(err) } if !ok { if err = os.Mkdir(this.CachePath, os.ModePerm); err != nil { //fmt.Println(err); } } //fmt.Println(this.getCacheFileName("123456")); } // get cached file name. it's md5 encoded. func (this *FileCache) getCacheFileName(key string) string { m := md5.New() io.WriteString(m, key) keyMd5 := hex.EncodeToString(m.Sum(nil)) cachePath := this.CachePath //fmt.Println("cachepath : " , cachePath) //fmt.Println("md5" , keyMd5); switch this.DirectoryLevel { case 2: cachePath = filepath.Join(cachePath, keyMd5[0:2], keyMd5[2:4]) case 1: cachePath = filepath.Join(cachePath, keyMd5[0:2]) } ok, err := exists(cachePath) if err != nil { //fmt.Println(err) } if !ok { if err = os.MkdirAll(cachePath, os.ModePerm); err != nil { //fmt.Println(err); } } return filepath.Join(cachePath, fmt.Sprintf("%s%s", keyMd5, this.FileSuffix)) } // Get value from file cache. // if non-exist or expired, return empty string. func (this *FileCache) Get(key string) interface{} { filename := this.getCacheFileName(key) filedata, err := File_get_contents(filename) //fmt.Println("get length:" , len(filedata)); if err != nil { return "" } var to FileCacheItem Gob_decode(filedata, &to) if to.Expired < time.Now().Unix() { return "" } return to.Data } // Put value into file cache. // timeout means how long to keep this file, unit of ms. // if timeout equals FileCacheEmbedExpiry(default is 0), cache this item forever. func (this *FileCache) Put(key string, val interface{}, timeout int64) error { gob.Register(val) filename := this.getCacheFileName(key) var item FileCacheItem item.Data = val if timeout == FileCacheEmbedExpiry { item.Expired = time.Now().Unix() + (86400 * 365 * 10) // ten years } else { item.Expired = time.Now().Unix() + timeout } item.Lastaccess = time.Now().Unix() data, err := Gob_encode(item) if err != nil { return err } err = File_put_contents(filename, data) return err } // Delete file cache value. func (this *FileCache) Delete(key string) error { filename := this.getCacheFileName(key) if ok, _ := exists(filename); ok { return os.Remove(filename) } return nil } // Increase cached int value. // this value is saving forever unless Delete. func (this *FileCache) Incr(key string) error { data := this.Get(key) var incr int //fmt.Println(reflect.TypeOf(data).Name()) if reflect.TypeOf(data).Name() != "int" { incr = 0 } else { incr = data.(int) + 1 } this.Put(key, incr, FileCacheEmbedExpiry) return nil } // Decrease cached int value. func (this *FileCache) Decr(key string) error { data := this.Get(key) var decr int if reflect.TypeOf(data).Name() != "int" || data.(int)-1 <= 0 { decr = 0 } else { decr = data.(int) - 1 } this.Put(key, decr, FileCacheEmbedExpiry) return nil } // Check value is exist. func (this *FileCache) IsExist(key string) bool { filename := this.getCacheFileName(key) ret, _ := exists(filename) return ret } // Clean cached files. // not implemented. func (this *FileCache) ClearAll() error { //this.CachePath return nil } // check file exist. func exists(path string) (bool, error) { _, err := os.Stat(path) if err == nil { return true, nil } if os.IsNotExist(err) { return false, nil } return false, err } // Get bytes to file. // if non-exist, create this file. func File_get_contents(filename string) ([]byte, error) { f, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE, os.ModePerm) if err != nil { return []byte(""), err } defer f.Close() stat, err := f.Stat() if err != nil { return []byte(""), err } data := make([]byte, stat.Size()) result, err := f.Read(data) if int64(result) == stat.Size() { return data, err } return []byte(""), err } // Put bytes to file. // if non-exist, create this file. func File_put_contents(filename string, content []byte) error { fp, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE, os.ModePerm) if err != nil { return err } defer fp.Close() _, err = fp.Write(content) return err } // Gob encodes file cache item. func Gob_encode(data interface{}) ([]byte, error) { buf := bytes.NewBuffer(nil) enc := gob.NewEncoder(buf) err := enc.Encode(data) if err != nil { return nil, err } return buf.Bytes(), err } // Gob decodes file cache item. func Gob_decode(data []byte, to *FileCacheItem) error { buf := bytes.NewBuffer(data) dec := gob.NewDecoder(buf) return dec.Decode(&to) }