// 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" "github.com/djherbis/atime" ) const ( sessDir string = "go-session" sessExt string = "gsd" ) var pder *ProviderFiles func init() { pder = &ProviderFiles{} //session.Register("files", pder) } 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 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 *SessionFile, 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() pder.list.PushBack(sid) return &SessionFile{sid}, nil } // Read return existing session by sid or new session if not exists func (pder *ProviderFiles) Read(sid string) (sess *SessionFile, err error) { pder.lock.Lock() defer pder.lock.Unlock() if pder.Exists(sid) { return &SessionFile{sid}, nil } return pder.Init(sid) } // 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 } // SessionFile save session data into files using gob encode/dacode type SessionFile struct { sid string } // load data from session file func (sf *SessionFile) load() (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 } // save data into session file func (sf *SessionFile) save(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.load(); 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.load(); err != nil { return err } data[k] = v return sf.save(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.load(); err != nil { return err } delete(data, k) return sf.save(data) } // SessionID return sid func (sf *SessionFile) SessionID() (sid string) { return sf.sid }