file.go 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. // Copyright 2013 Beego Authors
  2. // Copyright 2014 The Macaron Authors
  3. //
  4. // Licensed under the Apache License, Version 2.0 (the "License"): you may
  5. // not use this file except in compliance with the License. You may obtain
  6. // a copy of the License at
  7. //
  8. // http://www.apache.org/licenses/LICENSE-2.0
  9. //
  10. // Unless required by applicable law or agreed to in writing, software
  11. // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  12. // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  13. // License for the specific language governing permissions and limitations
  14. // under the License.
  15. package cache
  16. import (
  17. "crypto/md5"
  18. "encoding/hex"
  19. "fmt"
  20. "io/ioutil"
  21. "log"
  22. "os"
  23. "path/filepath"
  24. "sync"
  25. "time"
  26. "github.com/Unknwon/com"
  27. "gopkg.in/macaron.v1"
  28. )
  29. // Item represents a cache item.
  30. type Item struct {
  31. Val interface{}
  32. Created int64
  33. Expire int64
  34. }
  35. func (item *Item) hasExpired() bool {
  36. return item.Expire > 0 &&
  37. (time.Now().Unix()-item.Created) >= item.Expire
  38. }
  39. // FileCacher represents a file cache adapter implementation.
  40. type FileCacher struct {
  41. lock sync.Mutex
  42. rootPath string
  43. interval int // GC interval.
  44. }
  45. // NewFileCacher creates and returns a new file cacher.
  46. func NewFileCacher() *FileCacher {
  47. return &FileCacher{}
  48. }
  49. func (c *FileCacher) filepath(key string) string {
  50. m := md5.Sum([]byte(key))
  51. hash := hex.EncodeToString(m[:])
  52. return filepath.Join(c.rootPath, string(hash[0]), string(hash[1]), hash)
  53. }
  54. // Put puts value into cache with key and expire time.
  55. // If expired is 0, it will not be deleted by GC.
  56. func (c *FileCacher) Put(key string, val interface{}, expire int64) error {
  57. filename := c.filepath(key)
  58. item := &Item{val, time.Now().Unix(), expire}
  59. data, err := EncodeGob(item)
  60. if err != nil {
  61. return err
  62. }
  63. os.MkdirAll(filepath.Dir(filename), os.ModePerm)
  64. return ioutil.WriteFile(filename, data, os.ModePerm)
  65. }
  66. func (c *FileCacher) read(key string) (*Item, error) {
  67. filename := c.filepath(key)
  68. data, err := ioutil.ReadFile(filename)
  69. if err != nil {
  70. return nil, err
  71. }
  72. item := new(Item)
  73. return item, DecodeGob(data, item)
  74. }
  75. // Get gets cached value by given key.
  76. func (c *FileCacher) Get(key string) interface{} {
  77. item, err := c.read(key)
  78. if err != nil {
  79. return nil
  80. }
  81. if item.hasExpired() {
  82. os.Remove(c.filepath(key))
  83. return nil
  84. }
  85. return item.Val
  86. }
  87. // Delete deletes cached value by given key.
  88. func (c *FileCacher) Delete(key string) error {
  89. return os.Remove(c.filepath(key))
  90. }
  91. // Incr increases cached int-type value by given key as a counter.
  92. func (c *FileCacher) Incr(key string) error {
  93. item, err := c.read(key)
  94. if err != nil {
  95. return err
  96. }
  97. item.Val, err = Incr(item.Val)
  98. if err != nil {
  99. return err
  100. }
  101. return c.Put(key, item.Val, item.Expire)
  102. }
  103. // Decrease cached int value.
  104. func (c *FileCacher) Decr(key string) error {
  105. item, err := c.read(key)
  106. if err != nil {
  107. return err
  108. }
  109. item.Val, err = Decr(item.Val)
  110. if err != nil {
  111. return err
  112. }
  113. return c.Put(key, item.Val, item.Expire)
  114. }
  115. // IsExist returns true if cached value exists.
  116. func (c *FileCacher) IsExist(key string) bool {
  117. return com.IsExist(c.filepath(key))
  118. }
  119. // Flush deletes all cached data.
  120. func (c *FileCacher) Flush() error {
  121. return os.RemoveAll(c.rootPath)
  122. }
  123. func (c *FileCacher) startGC() {
  124. c.lock.Lock()
  125. defer c.lock.Unlock()
  126. if c.interval < 1 {
  127. return
  128. }
  129. if err := filepath.Walk(c.rootPath, func(path string, fi os.FileInfo, err error) error {
  130. if err != nil {
  131. return fmt.Errorf("Walk: %v", err)
  132. }
  133. if fi.IsDir() {
  134. return nil
  135. }
  136. data, err := ioutil.ReadFile(path)
  137. if err != nil && !os.IsNotExist(err) {
  138. fmt.Errorf("ReadFile: %v", err)
  139. }
  140. item := new(Item)
  141. if err = DecodeGob(data, item); err != nil {
  142. return err
  143. }
  144. if item.hasExpired() {
  145. if err = os.Remove(path); err != nil && !os.IsNotExist(err) {
  146. return fmt.Errorf("Remove: %v", err)
  147. }
  148. }
  149. return nil
  150. }); err != nil {
  151. log.Printf("error garbage collecting cache files: %v", err)
  152. }
  153. time.AfterFunc(time.Duration(c.interval)*time.Second, func() { c.startGC() })
  154. }
  155. // StartAndGC starts GC routine based on config string settings.
  156. func (c *FileCacher) StartAndGC(opt Options) error {
  157. c.lock.Lock()
  158. c.rootPath = opt.AdapterConfig
  159. c.interval = opt.Interval
  160. if !filepath.IsAbs(c.rootPath) {
  161. c.rootPath = filepath.Join(macaron.Root, c.rootPath)
  162. }
  163. c.lock.Unlock()
  164. if err := os.MkdirAll(c.rootPath, os.ModePerm); err != nil {
  165. return err
  166. }
  167. go c.startGC()
  168. return nil
  169. }
  170. func init() {
  171. Register("file", NewFileCacher())
  172. }