225 lines
5.6 KiB
Go
225 lines
5.6 KiB
Go
// Package files implements sessions saved into filesystem persistently encoded using gob
|
|
package files
|
|
|
|
import (
|
|
"bytes"
|
|
"container/list"
|
|
"encoding/gob"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"sync"
|
|
"time"
|
|
|
|
//"git.mtux.eu/darkgopher/session"
|
|
"git.mtux.eu/darkgopher/session"
|
|
"github.com/djherbis/atime"
|
|
)
|
|
|
|
const (
|
|
sessDir string = "go-session"
|
|
sessExt string = "gsd"
|
|
)
|
|
|
|
var pder *ProviderFiles
|
|
|
|
func init() {
|
|
pder = &ProviderFiles{}
|
|
session.Register("files", pder)
|
|
}
|
|
|
|
// ckdirpath return filename from sid
|
|
func ckdirpath(sid string) string {
|
|
return fmt.Sprintf("%s/%s/%s.%s", pder.sessPath, sessDir, sid, sessExt)
|
|
}
|
|
|
|
// ProviderFiles implement filesystem session provider
|
|
type ProviderFiles struct {
|
|
lock sync.Mutex
|
|
sessions map[string]*list.Element
|
|
list *list.List //or gc
|
|
sessPath string
|
|
}
|
|
|
|
func (pder *ProviderFiles) updateAtime(sid string) (err error) {
|
|
pth := ckdirpath(sid)
|
|
return os.Chtimes(pth, time.Now(), time.Time{})
|
|
}
|
|
|
|
func (pder *ProviderFiles) getAtime(sid string) (atm time.Time, err error) {
|
|
pth := ckdirpath(sid)
|
|
return atime.Stat(pth)
|
|
}
|
|
|
|
// SetParams for files session provider set base path in filesystem for save sessions
|
|
func (pder *ProviderFiles) SetParams(p any) (err error) {
|
|
if p != nil {
|
|
if s, ok := p.(string); ok {
|
|
pder.sessPath = s
|
|
return
|
|
}
|
|
return fmt.Errorf("parameter for files session provider is not string")
|
|
}
|
|
return fmt.Errorf("parameter for files session provider must be provided, not nil")
|
|
}
|
|
|
|
// Init create empty session file if not exists and retturn *Session
|
|
func (pder *ProviderFiles) Init(sid string) (sess session.Session, err error) {
|
|
pder.lock.Lock()
|
|
defer pder.lock.Unlock()
|
|
var fd *os.File
|
|
ckf := ckdirpath(sid)
|
|
if fd, err = os.Create(ckf); err != nil {
|
|
return nil, fmt.Errorf("create session file: %s failed with err: %w", ckf, err)
|
|
}
|
|
defer fd.Close()
|
|
sess = &SessionFile{sid}
|
|
ssel := pder.list.PushBack(sess)
|
|
pder.sessions[sid] = ssel
|
|
return sess, nil
|
|
}
|
|
|
|
// Load return existing session by sid or new session if not exists
|
|
func (pder *ProviderFiles) Load(sid string) (sess session.Session, err error) {
|
|
pder.lock.Lock()
|
|
defer pder.lock.Unlock()
|
|
if pder.Exists(sid) {
|
|
return pder.sessions[sid].Value.(*SessionFile), nil
|
|
}
|
|
return pder.Init(sid)
|
|
}
|
|
|
|
// Destroy remove session file
|
|
func (pder *ProviderFiles) Destroy(sid string) (err error) {
|
|
pder.lock.Lock()
|
|
defer pder.lock.Unlock()
|
|
if pder.Exists(sid) {
|
|
ssel := pder.sessions[sid]
|
|
delete(pder.sessions, sid)
|
|
pder.list.Remove(ssel)
|
|
return os.Remove(ckdirpath(sid))
|
|
}
|
|
return
|
|
}
|
|
|
|
// ChangeID change oldsid to newsid and preserve session data
|
|
func (pder *ProviderFiles) ChangeID(oldsid, newsid string) (err error) {
|
|
if ssel, ok := pder.sessions[oldsid]; ok {
|
|
ckfold := ckdirpath(oldsid)
|
|
ckfnew := ckdirpath(newsid)
|
|
if err = os.Rename(ckfold, ckfnew); err != nil {
|
|
return fmt.Errorf("Rename cookie file: %s to: %s failed: %v", ckfold, ckfnew, err)
|
|
}
|
|
ssel.Value.(*SessionFile).sid = newsid
|
|
delete(pder.sessions, oldsid)
|
|
pder.sessions[newsid] = ssel
|
|
}
|
|
return
|
|
}
|
|
|
|
// Exists check if session sid exists in storage
|
|
func (pder *ProviderFiles) Exists(sid string) bool {
|
|
ckf := ckdirpath(sid)
|
|
if _, err := os.Stat(ckf); errors.Is(err, os.ErrExist) {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
// GC periodically remove old sessions rom storages
|
|
func (pder *ProviderFiles) GC(maxlifetime int64) {
|
|
pder.lock.Lock()
|
|
defer pder.lock.Unlock()
|
|
for {
|
|
ssel := pder.list.Back()
|
|
if ssel == nil {
|
|
break
|
|
}
|
|
sid := ssel.Value.(*SessionFile).sid
|
|
var at time.Time
|
|
var err error
|
|
if at, err = pder.getAtime(sid); err != nil {
|
|
continue
|
|
}
|
|
if at.UnixMilli()+(maxlifetime*session.MilisPerSec()) < time.Now().UnixMilli() {
|
|
pder.list.Remove(ssel)
|
|
delete(pder.sessions, sid)
|
|
ckf := ckdirpath(sid)
|
|
os.Remove(ckf)
|
|
}
|
|
}
|
|
}
|
|
|
|
// SessionFile save session data into files using gob encode/dacode
|
|
type SessionFile struct {
|
|
sid string
|
|
}
|
|
|
|
// loadFromFile data from session file
|
|
func (sf *SessionFile) loadFromFile() (data map[any]any, err error) {
|
|
var sb []byte
|
|
sfp := ckdirpath(sf.sid)
|
|
if sb, err = os.ReadFile(sfp); err != nil {
|
|
return nil, fmt.Errorf("session file: %s read error: %v", sf.sid, err)
|
|
}
|
|
var gobdata bytes.Buffer
|
|
if _, err = gobdata.Write(sb); err != nil {
|
|
return nil, fmt.Errorf("load session file: %s into buffer err: %v", sf.sid, err)
|
|
}
|
|
dec := gob.NewDecoder(&gobdata)
|
|
if err = dec.Decode(&data); err != nil {
|
|
return nil, fmt.Errorf("decode gob data from file: %s error: %v", sf.sid, err)
|
|
}
|
|
return
|
|
}
|
|
|
|
// saveToFile data into session file
|
|
func (sf *SessionFile) saveToFile(data map[any]any) (err error) {
|
|
var gobdata bytes.Buffer
|
|
enc := gob.NewEncoder(&gobdata)
|
|
if err = enc.Encode(data); err != nil {
|
|
return fmt.Errorf("gob encode file: %s error: %v", sf.sid, err)
|
|
}
|
|
sfp := ckdirpath(sf.sid)
|
|
if err = os.WriteFile(sfp, gobdata.Bytes(), 0o600); err != nil {
|
|
return fmt.Errorf("write gob data into file: %s error: %v", sf.sid, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Get value from session key k
|
|
func (sf *SessionFile) Get(k any) (v any, err error) {
|
|
defer pder.updateAtime(sf.sid)
|
|
var data map[any]any
|
|
if data, err = sf.loadFromFile(); err != nil {
|
|
return nil, err
|
|
}
|
|
return data[k], nil
|
|
}
|
|
|
|
// Set value of key k to v
|
|
func (sf *SessionFile) Set(k, v any) (err error) {
|
|
defer pder.updateAtime(sf.sid)
|
|
var data map[any]any
|
|
if data, err = sf.loadFromFile(); err != nil {
|
|
return err
|
|
}
|
|
data[k] = v
|
|
return sf.saveToFile(data)
|
|
}
|
|
|
|
// Delete remove value of key k from session
|
|
func (sf *SessionFile) Delete(k any) (err error) {
|
|
defer pder.updateAtime(sf.sid)
|
|
var data map[any]any
|
|
if data, err = sf.loadFromFile(); err != nil {
|
|
return err
|
|
}
|
|
delete(data, k)
|
|
return sf.saveToFile(data)
|
|
}
|
|
|
|
// SessionID return sid
|
|
func (sf *SessionFile) SessionID() (sid string) {
|
|
return sf.sid
|
|
}
|