gosora/group_store.go
Azareal d0ffc4be78 Refactored a few bits and pieces.
Added Riot as a dependency, I'm still deciding over whether to use this or Bleve.
Tweaked the topic view for Cosora.
Fixed a crash bug in the group creator.
Moved a few things from permissions.go into the ForumPermsStore, more to come here!
Added more tests.
Refactored the MySQL Query Generator.
Refactored the BBCode Parser.
Moved the presets into the Phrase System.
2017-11-05 01:04:57 +00:00

353 lines
7.9 KiB
Go

/* Under Heavy Construction */
package main
import (
"database/sql"
"encoding/json"
"errors"
"log"
"sort"
"sync"
"./query_gen/lib"
)
var gstore GroupStore
// ? - We could fallback onto the database when an item can't be found in the cache?
type GroupStore interface {
LoadGroups() error
DirtyGet(id int) *Group
Get(id int) (*Group, error)
GetCopy(id int) (Group, error)
Exists(id int) bool
Create(name string, tag string, isAdmin bool, isMod bool, isBanned bool) (int, error)
GetAll() ([]*Group, error)
GetRange(lower int, higher int) ([]*Group, error)
Reload(id int) error // ? - Should we move this to GroupCache? It might require us to do some unnecessary casting though
GlobalCount() int
}
type GroupCache interface {
CacheSet(group *Group) error
Length() int
}
type MemoryGroupStore struct {
groups map[int]*Group // TODO: Use a sync.Map instead of a map?
groupCount int
get *sql.Stmt
sync.RWMutex
}
func NewMemoryGroupStore() (*MemoryGroupStore, error) {
getGroupStmt, err := qgen.Builder.SimpleSelect("users_groups", "name, permissions, plugin_perms, is_mod, is_admin, is_banned, tag", "gid = ?", "", "")
if err != nil {
return nil, err
}
return &MemoryGroupStore{
groups: make(map[int]*Group),
groupCount: 0,
get: getGroupStmt,
}, nil
}
func (mgs *MemoryGroupStore) LoadGroups() error {
mgs.Lock()
defer mgs.Unlock()
mgs.groups[0] = &Group{ID: 0, Name: "Unknown"}
rows, err := getGroupsStmt.Query()
if err != nil {
return err
}
defer rows.Close()
i := 1
for ; rows.Next(); i++ {
group := &Group{ID: 0}
err := rows.Scan(&group.ID, &group.Name, &group.PermissionsText, &group.PluginPermsText, &group.IsMod, &group.IsAdmin, &group.IsBanned, &group.Tag)
if err != nil {
return err
}
err = mgs.initGroup(group)
if err != nil {
return err
}
mgs.groups[group.ID] = group
}
err = rows.Err()
if err != nil {
return err
}
mgs.groupCount = i
if dev.DebugMode {
log.Print("Binding the Not Loggedin Group")
}
GuestPerms = mgs.dirtyGetUnsafe(6).Perms
return nil
}
// TODO: Hit the database when the item isn't in memory
func (mgs *MemoryGroupStore) dirtyGetUnsafe(gid int) *Group {
group, ok := mgs.groups[gid]
if !ok {
return &blankGroup
}
return group
}
// TODO: Hit the database when the item isn't in memory
func (mgs *MemoryGroupStore) DirtyGet(gid int) *Group {
mgs.RLock()
group, ok := mgs.groups[gid]
mgs.RUnlock()
if !ok {
return &blankGroup
}
return group
}
// TODO: Hit the database when the item isn't in memory
func (mgs *MemoryGroupStore) Get(gid int) (*Group, error) {
mgs.RLock()
group, ok := mgs.groups[gid]
mgs.RUnlock()
if !ok {
return nil, ErrNoRows
}
return group, nil
}
// TODO: Hit the database when the item isn't in memory
func (mgs *MemoryGroupStore) GetCopy(gid int) (Group, error) {
mgs.RLock()
group, ok := mgs.groups[gid]
mgs.RUnlock()
if !ok {
return blankGroup, ErrNoRows
}
return *group, nil
}
func (mgs *MemoryGroupStore) Reload(id int) error {
var group = &Group{ID: id}
err := mgs.get.QueryRow(id).Scan(&group.Name, &group.PermissionsText, &group.PluginPermsText, &group.IsMod, &group.IsAdmin, &group.IsBanned, &group.Tag)
if err != nil {
return err
}
err = mgs.initGroup(group)
if err != nil {
LogError(err)
}
mgs.CacheSet(group)
err = rebuildGroupPermissions(id)
if err != nil {
LogError(err)
}
return nil
}
func (mgs *MemoryGroupStore) initGroup(group *Group) error {
err := json.Unmarshal(group.PermissionsText, &group.Perms)
if err != nil {
log.Printf("group: %+v\n", group)
log.Print("bad group perms: ", group.PermissionsText)
return err
}
if dev.DebugMode {
log.Printf(group.Name+": %+v\n", group.Perms)
}
err = json.Unmarshal(group.PluginPermsText, &group.PluginPerms)
if err != nil {
log.Printf("group: %+v\n", group)
log.Print("bad group plugin perms: ", group.PluginPermsText)
return err
}
if dev.DebugMode {
log.Printf(group.Name+": %+v\n", group.PluginPerms)
}
//group.Perms.ExtData = make(map[string]bool)
// TODO: Can we optimise the bit where this cascades down to the user now?
if group.IsAdmin || group.IsMod {
group.IsBanned = false
}
return nil
}
func (mgs *MemoryGroupStore) CacheSet(group *Group) error {
mgs.Lock()
mgs.groups[group.ID] = group
mgs.Unlock()
return nil
}
// TODO: Hit the database when the item isn't in memory
func (mgs *MemoryGroupStore) Exists(gid int) bool {
mgs.RLock()
group, ok := mgs.groups[gid]
mgs.RUnlock()
return ok && group.Name != ""
}
// ? Allow two groups with the same name?
func (mgs *MemoryGroupStore) Create(name string, tag string, isAdmin bool, isMod bool, isBanned bool) (gid int, err error) {
var permstr = "{}"
tx, err := db.Begin()
if err != nil {
return 0, err
}
defer tx.Rollback()
insertTx, err := qgen.Builder.SimpleInsertTx(tx, "users_groups", "name, tag, is_admin, is_mod, is_banned, permissions, plugin_perms", "?,?,?,?,?,?,'{}'")
if err != nil {
return 0, err
}
res, err := insertTx.Exec(name, tag, isAdmin, isMod, isBanned, permstr)
if err != nil {
return 0, err
}
gid64, err := res.LastInsertId()
if err != nil {
return 0, err
}
gid = int(gid64)
var perms = BlankPerms
var blankForums []ForumPerms
var blankIntList []int
var pluginPerms = make(map[string]bool)
var pluginPermsBytes = []byte("{}")
if vhooks["create_group_preappend"] != nil {
runVhook("create_group_preappend", &pluginPerms, &pluginPermsBytes)
}
// Generate the forum permissions based on the presets...
fdata, err := fstore.GetAll()
if err != nil {
return 0, err
}
var presetSet = make(map[int]string)
var permSet = make(map[int]ForumPerms)
permUpdateMutex.Lock()
defer permUpdateMutex.Unlock()
for _, forum := range fdata {
var thePreset string
if isAdmin {
thePreset = "admins"
} else if isMod {
thePreset = "staff"
} else if isBanned {
thePreset = "banned"
} else {
thePreset = "members"
}
permmap := presetToPermmap(forum.Preset)
permItem := permmap[thePreset]
permItem.Overrides = true
permSet[forum.ID] = permItem
presetSet[forum.ID] = forum.Preset
}
err = replaceForumPermsForGroupTx(tx, gid, presetSet, permSet)
if err != nil {
return 0, err
}
err = tx.Commit()
if err != nil {
return 0, err
}
// TODO: Can we optimise the bit where this cascades down to the user now?
if isAdmin || isMod {
isBanned = false
}
mgs.Lock()
mgs.groups[gid] = &Group{gid, name, isMod, isAdmin, isBanned, tag, perms, []byte(permstr), pluginPerms, pluginPermsBytes, blankForums, blankIntList}
mgs.groupCount++
mgs.Unlock()
for _, forum := range fdata {
err = fpstore.Reload(forum.ID)
if err != nil {
return gid, err
}
}
return gid, nil
}
func (mgs *MemoryGroupStore) GetAll() (results []*Group, err error) {
var i int
mgs.RLock()
results = make([]*Group, len(mgs.groups))
for _, group := range mgs.groups {
results[i] = group
i++
}
mgs.RUnlock()
sort.Sort(SortGroup(results))
return results, nil
}
func (mgs *MemoryGroupStore) GetAllMap() (map[int]*Group, error) {
mgs.RLock()
defer mgs.RUnlock()
return mgs.groups, nil
}
// ? - Set the lower and higher numbers to 0 to remove the bounds
// TODO: Might be a little slow right now, maybe we can cache the groups in a slice or break the map up into chunks
func (mgs *MemoryGroupStore) GetRange(lower int, higher int) (groups []*Group, err error) {
if lower == 0 && higher == 0 {
return mgs.GetAll()
}
if lower == 0 {
if higher < 0 {
return nil, errors.New("higher may not be lower than 0")
}
} else if higher == 0 {
if lower < 0 {
return nil, errors.New("lower may not be lower than 0")
}
}
mgs.RLock()
for gid, group := range mgs.groups {
if gid >= lower && (gid <= higher || higher == 0) {
groups = append(groups, group)
}
}
mgs.RUnlock()
sort.Sort(SortGroup(groups))
return groups, nil
}
func (mgs *MemoryGroupStore) Length() int {
mgs.RLock()
defer mgs.RUnlock()
return mgs.groupCount
}
func (mgs *MemoryGroupStore) GlobalCount() int {
mgs.RLock()
defer mgs.RUnlock()
return mgs.groupCount
}