diff --git a/cmd/gonic/main.go b/cmd/gonic/main.go index 3c20c36..ed6b044 100644 --- a/cmd/gonic/main.go +++ b/cmd/gonic/main.go @@ -70,6 +70,7 @@ func main() { }) var g run.Group g.Add(server.StartHTTP(*listenAddr)) + g.Add(server.StartSessionClean(10 * time.Minute)) if *scanInterval > 0 { tickerDur := time.Duration(*scanInterval) * time.Minute g.Add(server.StartScanTicker(tickerDur)) diff --git a/server/ctrladmin/ctrl.go b/server/ctrladmin/ctrl.go index 7e0b694..81a5828 100644 --- a/server/ctrladmin/ctrl.go +++ b/server/ctrladmin/ctrl.go @@ -15,7 +15,6 @@ import ( "github.com/Masterminds/sprig" "github.com/dustin/go-humanize" - "github.com/gorilla/securecookie" "github.com/gorilla/sessions" "github.com/oxtoacart/bpool" "github.com/wader/gormstore" @@ -85,12 +84,7 @@ type Controller struct { sessDB *gormstore.Store } -func New(b *ctrlbase.Controller) *Controller { - sessionKey := []byte(b.DB.GetSetting("session_key")) - if len(sessionKey) == 0 { - sessionKey = securecookie.GenerateRandomKey(32) - b.DB.SetSetting("session_key", string(sessionKey)) - } +func New(b *ctrlbase.Controller, sessDB *gormstore.Store) *Controller { tmplBase := template. New("layout"). Funcs(sprig.FuncMap()). @@ -100,9 +94,6 @@ func New(b *ctrlbase.Controller) *Controller { }) tmplBase = extendFromPaths(tmplBase, prefixPartials) tmplBase = extendFromPaths(tmplBase, prefixLayouts) - sessDB := gormstore.New(b.DB.DB, sessionKey) - sessDB.SessionOpts.HttpOnly = true - sessDB.SessionOpts.SameSite = http.SameSiteLaxMode return &Controller{ Controller: b, buffPool: bpool.NewBufferPool(64), diff --git a/server/db/db.go b/server/db/db.go index f710400..d1bf57a 100644 --- a/server/db/db.go +++ b/server/db/db.go @@ -6,6 +6,7 @@ import ( "net/url" "os" + "github.com/gorilla/securecookie" "github.com/jinzhu/gorm" "gopkg.in/gormigrate.v1" @@ -101,6 +102,15 @@ func (db *DB) SetSetting(key, value string) { FirstOrCreate(&Setting{}) } +func (db *DB) GetOrCreateKey(key string) string { + value := db.GetSetting(key) + if value == "" { + value = string(securecookie.GenerateRandomKey(32)) + db.SetSetting(key, value) + } + return value +} + func (db *DB) GetUserFromName(name string) *User { user := &User{} err := db. @@ -113,27 +123,21 @@ func (db *DB) GetUserFromName(name string) *User { return user } -func (db *DB) WithTx(cb func(*gorm.DB)) { - tx := db.Begin() - defer tx.Commit() - cb(tx) -} - type ChunkFunc func(*gorm.DB, []int64) error -func (db *DB) WithTxChunked(data []int64, cb ChunkFunc) error { +func (db *DB) TransactionChunked(data []int64, cb ChunkFunc) error { // https://sqlite.org/limits.html const size = 999 - tx := db.Begin() - defer tx.Commit() - for i := 0; i < len(data); i += size { - end := i + size - if end > len(data) { - end = len(data) + return db.Transaction(func(tx *gorm.DB) error { + for i := 0; i < len(data); i += size { + end := i + size + if end > len(data) { + end = len(data) + } + if err := cb(tx, data[i:end]); err != nil { + return err + } } - if err := cb(tx, data[i:end]); err != nil { - return err - } - } - return nil + return nil + }) } diff --git a/server/scanner/scanner.go b/server/scanner/scanner.go index 2ae5536..8802a74 100644 --- a/server/scanner/scanner.go +++ b/server/scanner/scanner.go @@ -104,7 +104,7 @@ func (s *Scanner) cleanTracks() (int, error) { missing = append(missing, int64(prev)) } } - err = s.db.WithTxChunked(missing, func(tx *gorm.DB, chunk []int64) error { + err = s.db.TransactionChunked(missing, func(tx *gorm.DB, chunk []int64) error { return tx.Where(chunk).Delete(&db.Track{}).Error }) return len(missing), err @@ -125,7 +125,7 @@ func (s *Scanner) cleanFolders() (int, error) { missing = append(missing, int64(prev)) } } - err = s.db.WithTxChunked(missing, func(tx *gorm.DB, chunk []int64) error { + err = s.db.TransactionChunked(missing, func(tx *gorm.DB, chunk []int64) error { return tx.Where(chunk).Delete(&db.Album{}).Error }) return len(missing), err diff --git a/server/server.go b/server/server.go index 1d68d40..945ef17 100644 --- a/server/server.go +++ b/server/server.go @@ -9,6 +9,7 @@ import ( "time" "github.com/gorilla/mux" + "github.com/wader/gormstore" "go.senan.xyz/gonic/server/assets" "go.senan.xyz/gonic/server/ctrladmin" @@ -30,6 +31,7 @@ type Server struct { scanner *scanner.Scanner jukebox *jukebox.Jukebox router *mux.Router + sessDB *gormstore.Store } func New(opts Options) *Server { @@ -51,7 +53,13 @@ func New(opts Options) *Server { r := mux.NewRouter() r.Use(base.WithLogging) r.Use(base.WithCORS) - ctrlAdmin := ctrladmin.New(base) + // + sessKey := opts.DB.GetOrCreateKey("session_key") + sessDB := gormstore.New(opts.DB.DB, []byte(sessKey)) + sessDB.SessionOpts.HttpOnly = true + sessDB.SessionOpts.SameSite = http.SameSiteLaxMode + // + ctrlAdmin := ctrladmin.New(base, sessDB) ctrlSubsonic := &ctrlsubsonic.Controller{ Controller: base, CachePath: opts.CachePath, @@ -65,6 +73,7 @@ func New(opts Options) *Server { scanner: scanner, jukebox: jukebox, router: r, + sessDB: sessDB, } } @@ -234,3 +243,26 @@ func (s *Server) StartJukebox() (FuncExecute, FuncInterrupt) { s.jukebox.Quit() } } + +func (s *Server) StartSessionClean(dur time.Duration) (FuncExecute, FuncInterrupt) { + ticker := time.NewTicker(dur) + done := make(chan struct{}) + waitFor := func() error { + for { + select { + case <-done: + return nil + case <-ticker.C: + s.sessDB.Cleanup() + } + } + } + return func() error { + log.Printf("starting job 'session clean'\n") + return waitFor() + }, func(_ error) { + // stop job + ticker.Stop() + done <- struct{}{} + } +}