附近小店
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

file.go 6.0 KiB

3 months ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  1. package cache
  2. import (
  3. "bytes"
  4. "crypto/md5"
  5. "encoding/gob"
  6. "encoding/hex"
  7. "encoding/json"
  8. "fmt"
  9. "io"
  10. "io/ioutil"
  11. "os"
  12. "path/filepath"
  13. "reflect"
  14. "strconv"
  15. "time"
  16. )
  17. // FileCacheItem is basic unit of file cache adapter.
  18. // it contains data and expire time.
  19. type FileCacheItem struct {
  20. Data interface{}
  21. LastAccess time.Time
  22. Expired time.Time
  23. }
  24. // FileCache Config
  25. var (
  26. FileCachePath = "cache" // cache directory
  27. FileCacheFileSuffix = ".bin" // cache file suffix
  28. FileCacheDirectoryLevel = 2 // cache file deep level if auto generated cache files.
  29. FileCacheEmbedExpiry time.Duration // cache expire time, default is no expire forever.
  30. )
  31. // FileCache is cache adapter for file storage.
  32. type FileCache struct {
  33. CachePath string
  34. FileSuffix string
  35. DirectoryLevel int
  36. EmbedExpiry int
  37. }
  38. // NewFileCache Create new file cache with no config.
  39. // the level and expiry need set in method StartAndGC as config string.
  40. func NewFileCache() Cache {
  41. // return &FileCache{CachePath:FileCachePath, FileSuffix:FileCacheFileSuffix}
  42. return &FileCache{}
  43. }
  44. // StartAndGC will start and begin gc for file cache.
  45. // the config need to be like {CachePath:"/cache","FileSuffix":".bin","DirectoryLevel":2,"EmbedExpiry":0}
  46. func (fc *FileCache) StartAndGC(config string) error {
  47. var cfg map[string]string
  48. json.Unmarshal([]byte(config), &cfg)
  49. if _, ok := cfg["CachePath"]; !ok {
  50. cfg["CachePath"] = FileCachePath
  51. }
  52. if _, ok := cfg["FileSuffix"]; !ok {
  53. cfg["FileSuffix"] = FileCacheFileSuffix
  54. }
  55. if _, ok := cfg["DirectoryLevel"]; !ok {
  56. cfg["DirectoryLevel"] = strconv.Itoa(FileCacheDirectoryLevel)
  57. }
  58. if _, ok := cfg["EmbedExpiry"]; !ok {
  59. cfg["EmbedExpiry"] = strconv.FormatInt(int64(FileCacheEmbedExpiry.Seconds()), 10)
  60. }
  61. fc.CachePath = cfg["CachePath"]
  62. fc.FileSuffix = cfg["FileSuffix"]
  63. fc.DirectoryLevel, _ = strconv.Atoi(cfg["DirectoryLevel"])
  64. fc.EmbedExpiry, _ = strconv.Atoi(cfg["EmbedExpiry"])
  65. fc.Init()
  66. return nil
  67. }
  68. // Init will make new dir for file cache if not exist.
  69. func (fc *FileCache) Init() {
  70. if ok, _ := exists(fc.CachePath); !ok { // todo : error handle
  71. _ = os.MkdirAll(fc.CachePath, os.ModePerm) // todo : error handle
  72. }
  73. }
  74. // get cached file name. it's md5 encoded.
  75. func (fc *FileCache) getCacheFileName(key string) string {
  76. m := md5.New()
  77. io.WriteString(m, key)
  78. keyMd5 := hex.EncodeToString(m.Sum(nil))
  79. cachePath := fc.CachePath
  80. switch fc.DirectoryLevel {
  81. case 2:
  82. cachePath = filepath.Join(cachePath, keyMd5[0:2], keyMd5[2:4])
  83. case 1:
  84. cachePath = filepath.Join(cachePath, keyMd5[0:2])
  85. }
  86. if ok, _ := exists(cachePath); !ok { // todo : error handle
  87. _ = os.MkdirAll(cachePath, os.ModePerm) // todo : error handle
  88. }
  89. return filepath.Join(cachePath, fmt.Sprintf("%s%s", keyMd5, fc.FileSuffix))
  90. }
  91. // Get value from file cache.
  92. // if non-exist or expired, return empty string.
  93. func (fc *FileCache) Get(key string) interface{} {
  94. fileData, err := FileGetContents(fc.getCacheFileName(key))
  95. if err != nil {
  96. return ""
  97. }
  98. var to FileCacheItem
  99. GobDecode(fileData, &to)
  100. if to.Expired.Before(time.Now()) {
  101. return ""
  102. }
  103. return to.Data
  104. }
  105. // GetMulti gets values from file cache.
  106. // if non-exist or expired, return empty string.
  107. func (fc *FileCache) GetMulti(keys []string) []interface{} {
  108. var rc []interface{}
  109. for _, key := range keys {
  110. rc = append(rc, fc.Get(key))
  111. }
  112. return rc
  113. }
  114. // Put value into file cache.
  115. // timeout means how long to keep this file, unit of ms.
  116. // if timeout equals FileCacheEmbedExpiry(default is 0), cache this item forever.
  117. func (fc *FileCache) Put(key string, val interface{}, timeout time.Duration) error {
  118. gob.Register(val)
  119. item := FileCacheItem{Data: val}
  120. if timeout == FileCacheEmbedExpiry {
  121. item.Expired = time.Now().Add((86400 * 365 * 10) * time.Second) // ten years
  122. } else {
  123. item.Expired = time.Now().Add(timeout)
  124. }
  125. item.LastAccess = time.Now()
  126. data, err := GobEncode(item)
  127. if err != nil {
  128. return err
  129. }
  130. return FilePutContents(fc.getCacheFileName(key), data)
  131. }
  132. // Delete file cache value.
  133. func (fc *FileCache) Delete(key string) error {
  134. filename := fc.getCacheFileName(key)
  135. if ok, _ := exists(filename); ok {
  136. return os.Remove(filename)
  137. }
  138. return nil
  139. }
  140. // Incr will increase cached int value.
  141. // fc value is saving forever unless Delete.
  142. func (fc *FileCache) Incr(key string) error {
  143. data := fc.Get(key)
  144. var incr int
  145. if reflect.TypeOf(data).Name() != "int" {
  146. incr = 0
  147. } else {
  148. incr = data.(int) + 1
  149. }
  150. fc.Put(key, incr, FileCacheEmbedExpiry)
  151. return nil
  152. }
  153. // Decr will decrease cached int value.
  154. func (fc *FileCache) Decr(key string) error {
  155. data := fc.Get(key)
  156. var decr int
  157. if reflect.TypeOf(data).Name() != "int" || data.(int)-1 <= 0 {
  158. decr = 0
  159. } else {
  160. decr = data.(int) - 1
  161. }
  162. fc.Put(key, decr, FileCacheEmbedExpiry)
  163. return nil
  164. }
  165. // IsExist check value is exist.
  166. func (fc *FileCache) IsExist(key string) bool {
  167. ret, _ := exists(fc.getCacheFileName(key))
  168. return ret
  169. }
  170. // ClearAll will clean cached files.
  171. // not implemented.
  172. func (fc *FileCache) ClearAll() error {
  173. return nil
  174. }
  175. // check file exist.
  176. func exists(path string) (bool, error) {
  177. _, err := os.Stat(path)
  178. if err == nil {
  179. return true, nil
  180. }
  181. if os.IsNotExist(err) {
  182. return false, nil
  183. }
  184. return false, err
  185. }
  186. // FileGetContents Get bytes to file.
  187. // if non-exist, create this file.
  188. func FileGetContents(filename string) (data []byte, e error) {
  189. return ioutil.ReadFile(filename)
  190. }
  191. // FilePutContents Put bytes to file.
  192. // if non-exist, create this file.
  193. func FilePutContents(filename string, content []byte) error {
  194. return ioutil.WriteFile(filename, content, os.ModePerm)
  195. }
  196. // GobEncode Gob encodes file cache item.
  197. func GobEncode(data interface{}) ([]byte, error) {
  198. buf := bytes.NewBuffer(nil)
  199. enc := gob.NewEncoder(buf)
  200. err := enc.Encode(data)
  201. if err != nil {
  202. return nil, err
  203. }
  204. return buf.Bytes(), err
  205. }
  206. // GobDecode Gob decodes file cache item.
  207. func GobDecode(data []byte, to *FileCacheItem) error {
  208. buf := bytes.NewBuffer(data)
  209. dec := gob.NewDecoder(buf)
  210. return dec.Decode(&to)
  211. }
  212. func init() {
  213. Register("file", NewFileCache)
  214. }