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 5.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  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 session
  16. import (
  17. "fmt"
  18. "io/ioutil"
  19. "log"
  20. "os"
  21. "path"
  22. "path/filepath"
  23. "sync"
  24. "time"
  25. "github.com/unknwon/com"
  26. )
  27. // FileStore represents a file session store implementation.
  28. type FileStore struct {
  29. p *FileProvider
  30. sid string
  31. lock sync.RWMutex
  32. data map[interface{}]interface{}
  33. }
  34. // NewFileStore creates and returns a file session store.
  35. func NewFileStore(p *FileProvider, sid string, kv map[interface{}]interface{}) *FileStore {
  36. return &FileStore{
  37. p: p,
  38. sid: sid,
  39. data: kv,
  40. }
  41. }
  42. // Set sets value to given key in session.
  43. func (s *FileStore) Set(key, val interface{}) error {
  44. s.lock.Lock()
  45. defer s.lock.Unlock()
  46. s.data[key] = val
  47. return nil
  48. }
  49. // Get gets value by given key in session.
  50. func (s *FileStore) Get(key interface{}) interface{} {
  51. s.lock.RLock()
  52. defer s.lock.RUnlock()
  53. return s.data[key]
  54. }
  55. // Delete delete a key from session.
  56. func (s *FileStore) Delete(key interface{}) error {
  57. s.lock.Lock()
  58. defer s.lock.Unlock()
  59. delete(s.data, key)
  60. return nil
  61. }
  62. // ID returns current session ID.
  63. func (s *FileStore) ID() string {
  64. return s.sid
  65. }
  66. // Release releases resource and save data to provider.
  67. func (s *FileStore) Release() error {
  68. s.p.lock.Lock()
  69. defer s.p.lock.Unlock()
  70. // Skip encoding if the data is empty
  71. if len(s.data) == 0 {
  72. return nil
  73. }
  74. data, err := EncodeGob(s.data)
  75. if err != nil {
  76. return err
  77. }
  78. return ioutil.WriteFile(s.p.filepath(s.sid), data, 0600)
  79. }
  80. // Flush deletes all session data.
  81. func (s *FileStore) Flush() error {
  82. s.lock.Lock()
  83. defer s.lock.Unlock()
  84. s.data = make(map[interface{}]interface{})
  85. return nil
  86. }
  87. // FileProvider represents a file session provider implementation.
  88. type FileProvider struct {
  89. lock sync.RWMutex
  90. maxlifetime int64
  91. rootPath string
  92. }
  93. // Init initializes file session provider with given root path.
  94. func (p *FileProvider) Init(maxlifetime int64, rootPath string) error {
  95. p.lock.Lock()
  96. p.maxlifetime = maxlifetime
  97. p.rootPath = rootPath
  98. p.lock.Unlock()
  99. return nil
  100. }
  101. func (p *FileProvider) filepath(sid string) string {
  102. return path.Join(p.rootPath, string(sid[0]), string(sid[1]), sid)
  103. }
  104. // Read returns raw session store by session ID.
  105. func (p *FileProvider) Read(sid string) (_ RawStore, err error) {
  106. filename := p.filepath(sid)
  107. if err = os.MkdirAll(path.Dir(filename), 0700); err != nil {
  108. return nil, err
  109. }
  110. p.lock.RLock()
  111. defer p.lock.RUnlock()
  112. var f *os.File
  113. ok := false
  114. if com.IsFile(filename) {
  115. modTime, err := com.FileMTime(filename)
  116. if err != nil {
  117. return nil, err
  118. }
  119. ok = (modTime + p.maxlifetime) >= time.Now().Unix()
  120. }
  121. if ok {
  122. f, err = os.OpenFile(filename, os.O_RDONLY, 0600)
  123. } else {
  124. f, err = os.Create(filename)
  125. }
  126. if err != nil {
  127. return nil, err
  128. }
  129. defer f.Close()
  130. if err = os.Chtimes(filename, time.Now(), time.Now()); err != nil {
  131. return nil, err
  132. }
  133. var kv map[interface{}]interface{}
  134. data, err := ioutil.ReadAll(f)
  135. if err != nil {
  136. return nil, err
  137. }
  138. if len(data) == 0 {
  139. kv = make(map[interface{}]interface{})
  140. } else {
  141. kv, err = DecodeGob(data)
  142. if err != nil {
  143. return nil, err
  144. }
  145. }
  146. return NewFileStore(p, sid, kv), nil
  147. }
  148. // Exist returns true if session with given ID exists.
  149. func (p *FileProvider) Exist(sid string) bool {
  150. p.lock.RLock()
  151. defer p.lock.RUnlock()
  152. return com.IsFile(p.filepath(sid))
  153. }
  154. // Destroy deletes a session by session ID.
  155. func (p *FileProvider) Destroy(sid string) error {
  156. p.lock.Lock()
  157. defer p.lock.Unlock()
  158. return os.Remove(p.filepath(sid))
  159. }
  160. func (p *FileProvider) regenerate(oldsid, sid string) (err error) {
  161. p.lock.Lock()
  162. defer p.lock.Unlock()
  163. filename := p.filepath(sid)
  164. if com.IsExist(filename) {
  165. return fmt.Errorf("new sid '%s' already exists", sid)
  166. }
  167. oldname := p.filepath(oldsid)
  168. if !com.IsFile(oldname) {
  169. data, err := EncodeGob(make(map[interface{}]interface{}))
  170. if err != nil {
  171. return err
  172. }
  173. if err = os.MkdirAll(path.Dir(oldname), 0700); err != nil {
  174. return err
  175. }
  176. if err = ioutil.WriteFile(oldname, data, 0600); err != nil {
  177. return err
  178. }
  179. }
  180. if err = os.MkdirAll(path.Dir(filename), 0700); err != nil {
  181. return err
  182. }
  183. if err = os.Rename(oldname, filename); err != nil {
  184. return err
  185. }
  186. return nil
  187. }
  188. // Regenerate regenerates a session store from old session ID to new one.
  189. func (p *FileProvider) Regenerate(oldsid, sid string) (_ RawStore, err error) {
  190. if err := p.regenerate(oldsid, sid); err != nil {
  191. return nil, err
  192. }
  193. return p.Read(sid)
  194. }
  195. // Count counts and returns number of sessions.
  196. func (p *FileProvider) Count() int {
  197. count := 0
  198. if err := filepath.Walk(p.rootPath, func(path string, fi os.FileInfo, err error) error {
  199. if err != nil {
  200. return err
  201. }
  202. if !fi.IsDir() {
  203. count++
  204. }
  205. return nil
  206. }); err != nil {
  207. log.Printf("error counting session files: %v", err)
  208. return 0
  209. }
  210. return count
  211. }
  212. // GC calls GC to clean expired sessions.
  213. func (p *FileProvider) GC() {
  214. p.lock.RLock()
  215. defer p.lock.RUnlock()
  216. if !com.IsExist(p.rootPath) {
  217. return
  218. }
  219. if err := filepath.Walk(p.rootPath, func(path string, fi os.FileInfo, err error) error {
  220. if err != nil {
  221. return err
  222. }
  223. if !fi.IsDir() &&
  224. (fi.ModTime().Unix()+p.maxlifetime) < time.Now().Unix() {
  225. return os.Remove(path)
  226. }
  227. return nil
  228. }); err != nil {
  229. log.Printf("error garbage collecting session files: %v", err)
  230. }
  231. }
  232. func init() {
  233. Register("file", &FileProvider{})
  234. }