381 lines
11 KiB
Go
381 lines
11 KiB
Go
/*
|
|
*
|
|
* Gosora Forum Store
|
|
* Copyright Azareal 2017 - 2018
|
|
*
|
|
*/
|
|
package main
|
|
|
|
import (
|
|
"database/sql"
|
|
"errors"
|
|
"log"
|
|
"sort"
|
|
"sync"
|
|
"sync/atomic"
|
|
|
|
"./query_gen/lib"
|
|
)
|
|
|
|
var forumUpdateMutex sync.Mutex
|
|
var forumCreateMutex sync.Mutex
|
|
var forumPerms map[int]map[int]ForumPerms // [gid][fid]Perms // TODO: Add an abstraction around this and make it more thread-safe
|
|
var fstore ForumStore
|
|
|
|
// ForumStore is an interface for accessing the forums and the metadata stored on them
|
|
type ForumStore interface {
|
|
LoadForums() error
|
|
DirtyGet(id int) *Forum
|
|
Get(id int) (*Forum, error)
|
|
GetCopy(id int) (Forum, error)
|
|
BypassGet(id int) (*Forum, error)
|
|
Reload(id int) error // ? - Should we move this to ForumCache? It might require us to do some unnecessary casting though
|
|
//Update(Forum) error
|
|
Delete(id int) error
|
|
IncrementTopicCount(id int) error
|
|
DecrementTopicCount(id int) error
|
|
UpdateLastTopic(topicName string, tid int, username string, uid int, time string, fid int) error
|
|
Exists(id int) bool
|
|
GetAll() ([]*Forum, error)
|
|
GetAllIDs() ([]int, error)
|
|
GetAllVisible() ([]*Forum, error)
|
|
GetAllVisibleIDs() ([]int, error)
|
|
//GetChildren(parentID int, parentType string) ([]*Forum,error)
|
|
//GetFirstChild(parentID int, parentType string) (*Forum,error)
|
|
Create(forumName string, forumDesc string, active bool, preset string) (int, error)
|
|
|
|
GetGlobalCount() int
|
|
}
|
|
|
|
type ForumCache interface {
|
|
CacheGet(id int) (*Forum, error)
|
|
CacheSet(forum *Forum) error
|
|
CacheDelete(id int)
|
|
}
|
|
|
|
// MemoryForumStore is a struct which holds an arbitrary number of forums in memory, usually all of them, although we might introduce functionality to hold a smaller subset in memory for sites with an extremely large number of forums
|
|
type MemoryForumStore struct {
|
|
forums sync.Map // map[int]*Forum
|
|
forumView atomic.Value // []*Forum
|
|
//fids []int
|
|
forumCount int
|
|
|
|
get *sql.Stmt
|
|
getAll *sql.Stmt
|
|
delete *sql.Stmt
|
|
getForumCount *sql.Stmt
|
|
}
|
|
|
|
// NewMemoryForumStore gives you a new instance of MemoryForumStore
|
|
func NewMemoryForumStore() *MemoryForumStore {
|
|
getStmt, err := qgen.Builder.SimpleSelect("forums", "name, desc, active, preset, parentID, parentType, topicCount, lastTopic, lastTopicID, lastReplyer, lastReplyerID, lastTopicTime", "fid = ?", "", "")
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
getAllStmt, err := qgen.Builder.SimpleSelect("forums", "fid, name, desc, active, preset, parentID, parentType, topicCount, lastTopic, lastTopicID, lastReplyer, lastReplyerID, lastTopicTime", "", "fid ASC", "")
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
deleteStmt, err := qgen.Builder.SimpleUpdate("forums", "name= '', active = 0", "fid = ?")
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
forumCountStmt, err := qgen.Builder.SimpleCount("forums", "name != ''", "")
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
return &MemoryForumStore{
|
|
get: getStmt,
|
|
getAll: getAllStmt,
|
|
delete: deleteStmt,
|
|
getForumCount: forumCountStmt,
|
|
}
|
|
}
|
|
|
|
// TODO: Add support for subforums
|
|
func (mfs *MemoryForumStore) LoadForums() error {
|
|
log.Print("Adding the uncategorised forum")
|
|
forumUpdateMutex.Lock()
|
|
defer forumUpdateMutex.Unlock()
|
|
|
|
var forumView []*Forum
|
|
addForum := func(forum *Forum) {
|
|
mfs.forums.Store(forum.ID, forum)
|
|
if forum.Active && forum.Name != "" && forum.ParentType == "" {
|
|
forumView = append(forumView, forum)
|
|
}
|
|
}
|
|
|
|
rows, err := getForumsStmt.Query()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer rows.Close()
|
|
|
|
var i = 0
|
|
for ; rows.Next(); i++ {
|
|
forum := Forum{ID: 0, Active: true, Preset: "all"}
|
|
err = rows.Scan(&forum.ID, &forum.Name, &forum.Desc, &forum.Active, &forum.Preset, &forum.ParentID, &forum.ParentType, &forum.TopicCount, &forum.LastTopic, &forum.LastTopicID, &forum.LastReplyer, &forum.LastReplyerID, &forum.LastTopicTime)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if forum.Name == "" {
|
|
if dev.DebugMode {
|
|
log.Print("Adding a placeholder forum")
|
|
}
|
|
} else {
|
|
log.Print("Adding the " + forum.Name + " forum")
|
|
}
|
|
|
|
forum.Link = buildForumURL(nameToSlug(forum.Name), forum.ID)
|
|
forum.LastTopicLink = buildTopicURL(nameToSlug(forum.LastTopic), forum.LastTopicID)
|
|
addForum(&forum)
|
|
}
|
|
mfs.forumCount = i
|
|
mfs.forumView.Store(forumView)
|
|
return rows.Err()
|
|
}
|
|
|
|
// TODO: Hide social groups too
|
|
func (mfs *MemoryForumStore) rebuildView() {
|
|
var forumView []*Forum
|
|
mfs.forums.Range(func(_ interface{}, value interface{}) bool {
|
|
forum := value.(*Forum)
|
|
// ? - ParentType blank means that it doesn't have a parent
|
|
if forum.Active && forum.Name != "" && forum.ParentType == "" {
|
|
forumView = append(forumView, forum)
|
|
}
|
|
return true
|
|
})
|
|
sort.Sort(SortForum(forumView))
|
|
mfs.forumView.Store(forumView)
|
|
}
|
|
|
|
func (mfs *MemoryForumStore) DirtyGet(id int) *Forum {
|
|
fint, ok := mfs.forums.Load(id)
|
|
if !ok || fint.(*Forum).Name == "" {
|
|
return &Forum{ID: -1, Name: ""}
|
|
}
|
|
return fint.(*Forum)
|
|
}
|
|
|
|
func (mfs *MemoryForumStore) CacheGet(id int) (*Forum, error) {
|
|
fint, ok := mfs.forums.Load(id)
|
|
if !ok || fint.(*Forum).Name == "" {
|
|
return nil, ErrNoRows
|
|
}
|
|
return fint.(*Forum), nil
|
|
}
|
|
|
|
func (mfs *MemoryForumStore) Get(id int) (*Forum, error) {
|
|
fint, ok := mfs.forums.Load(id)
|
|
if !ok || fint.(*Forum).Name == "" {
|
|
var forum = &Forum{ID: id}
|
|
err := mfs.get.QueryRow(id).Scan(&forum.Name, &forum.Desc, &forum.Active, &forum.Preset, &forum.TopicCount, &forum.LastTopic, &forum.LastTopicID, &forum.LastReplyer, &forum.LastReplyerID, &forum.LastTopicTime)
|
|
|
|
forum.Link = buildForumURL(nameToSlug(forum.Name), forum.ID)
|
|
forum.LastTopicLink = buildTopicURL(nameToSlug(forum.LastTopic), forum.LastTopicID)
|
|
return forum, err
|
|
}
|
|
return fint.(*Forum), nil
|
|
}
|
|
|
|
func (mfs *MemoryForumStore) GetCopy(id int) (Forum, error) {
|
|
fint, ok := mfs.forums.Load(id)
|
|
if !ok || fint.(*Forum).Name == "" {
|
|
var forum = Forum{ID: id}
|
|
err := mfs.get.QueryRow(id).Scan(&forum.Name, &forum.Desc, &forum.Active, &forum.Preset, &forum.TopicCount, &forum.LastTopic, &forum.LastTopicID, &forum.LastReplyer, &forum.LastReplyerID, &forum.LastTopicTime)
|
|
|
|
forum.Link = buildForumURL(nameToSlug(forum.Name), forum.ID)
|
|
forum.LastTopicLink = buildTopicURL(nameToSlug(forum.LastTopic), forum.LastTopicID)
|
|
return forum, err
|
|
}
|
|
return *fint.(*Forum), nil
|
|
}
|
|
|
|
func (mfs *MemoryForumStore) BypassGet(id int) (*Forum, error) {
|
|
var forum = Forum{ID: id}
|
|
err := mfs.get.QueryRow(id).Scan(&forum.Name, &forum.Desc, &forum.Active, &forum.Preset, &forum.TopicCount, &forum.LastTopic, &forum.LastTopicID, &forum.LastReplyer, &forum.LastReplyerID, &forum.LastTopicTime)
|
|
|
|
forum.Link = buildForumURL(nameToSlug(forum.Name), forum.ID)
|
|
forum.LastTopicLink = buildTopicURL(nameToSlug(forum.LastTopic), forum.LastTopicID)
|
|
return &forum, err
|
|
}
|
|
|
|
func (mfs *MemoryForumStore) Reload(id int) error {
|
|
var forum = Forum{ID: id}
|
|
err := mfs.get.QueryRow(id).Scan(&forum.Name, &forum.Desc, &forum.Active, &forum.Preset, &forum.TopicCount, &forum.LastTopic, &forum.LastTopicID, &forum.LastReplyer, &forum.LastReplyerID, &forum.LastTopicTime)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
forum.Link = buildForumURL(nameToSlug(forum.Name), forum.ID)
|
|
forum.LastTopicLink = buildTopicURL(nameToSlug(forum.LastTopic), forum.LastTopicID)
|
|
|
|
mfs.CacheSet(&forum)
|
|
return nil
|
|
}
|
|
|
|
func (mfs *MemoryForumStore) CacheSet(forum *Forum) error {
|
|
mfs.forums.Store(forum.ID, forum)
|
|
mfs.rebuildView()
|
|
return nil
|
|
}
|
|
|
|
// ! Has a randomised order
|
|
func (mfs *MemoryForumStore) GetAll() (forumView []*Forum, err error) {
|
|
mfs.forums.Range(func(_ interface{}, value interface{}) bool {
|
|
forumView = append(forumView, value.(*Forum))
|
|
return true
|
|
})
|
|
sort.Sort(SortForum(forumView))
|
|
return forumView, nil
|
|
}
|
|
|
|
// ? - Can we optimise the sorting?
|
|
func (mfs *MemoryForumStore) GetAllIDs() (ids []int, err error) {
|
|
mfs.forums.Range(func(_ interface{}, value interface{}) bool {
|
|
ids = append(ids, value.(*Forum).ID)
|
|
return true
|
|
})
|
|
sort.Ints(ids)
|
|
return ids, nil
|
|
}
|
|
|
|
func (mfs *MemoryForumStore) GetAllVisible() (forumView []*Forum, err error) {
|
|
forumView = mfs.forumView.Load().([]*Forum)
|
|
return forumView, nil
|
|
}
|
|
|
|
func (mfs *MemoryForumStore) GetAllVisibleIDs() ([]int, error) {
|
|
forumView := mfs.forumView.Load().([]*Forum)
|
|
var ids = make([]int, len(forumView))
|
|
for i := 0; i < len(forumView); i++ {
|
|
ids[i] = forumView[i].ID
|
|
}
|
|
return ids, nil
|
|
}
|
|
|
|
// TODO: Implement sub-forums.
|
|
/*func (mfs *MemoryForumStore) GetChildren(parentID int, parentType string) ([]*Forum,error) {
|
|
return nil, nil
|
|
}
|
|
func (mfs *MemoryForumStore) GetFirstChild(parentID int, parentType string) (*Forum,error) {
|
|
return nil, nil
|
|
}*/
|
|
|
|
// TODO: Add a query for this rather than hitting cache
|
|
func (mfs *MemoryForumStore) Exists(id int) bool {
|
|
forum, ok := mfs.forums.Load(id)
|
|
return ok && forum.(*Forum).Name != ""
|
|
}
|
|
|
|
// TODO: Batch deletions with name blanking? Is this necessary?
|
|
func (mfs *MemoryForumStore) CacheDelete(id int) {
|
|
mfs.forums.Delete(id)
|
|
mfs.rebuildView()
|
|
}
|
|
|
|
// TODO: Add a hook to allow plugin_socialgroups to detect when one of it's forums has just been deleted?
|
|
func (mfs *MemoryForumStore) Delete(id int) error {
|
|
if id == 1 {
|
|
return errors.New("You cannot delete the Reports forum")
|
|
}
|
|
forumUpdateMutex.Lock()
|
|
defer forumUpdateMutex.Unlock()
|
|
_, err := mfs.delete.Exec(id)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
mfs.CacheDelete(id)
|
|
return nil
|
|
}
|
|
|
|
// ! Is this racey?
|
|
func (mfs *MemoryForumStore) IncrementTopicCount(id int) error {
|
|
forum, err := mfs.Get(id)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = addTopicsToForumStmt.Exec(1, id)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
forum.TopicCount++
|
|
return nil
|
|
}
|
|
|
|
// ! Is this racey?
|
|
func (mfs *MemoryForumStore) DecrementTopicCount(id int) error {
|
|
forum, err := mfs.Get(id)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = removeTopicsFromForumStmt.Exec(1, id)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
forum.TopicCount--
|
|
return nil
|
|
}
|
|
|
|
// TODO: Have a pointer to the last topic rather than storing it on the forum itself
|
|
// ! Is this racey?
|
|
func (mfs *MemoryForumStore) UpdateLastTopic(topicName string, tid int, username string, uid int, time string, fid int) error {
|
|
forum, err := mfs.Get(fid)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
_, err = updateForumCacheStmt.Exec(topicName, tid, username, uid, fid)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
forum.LastTopic = topicName
|
|
forum.LastTopicID = tid
|
|
forum.LastReplyer = username
|
|
forum.LastReplyerID = uid
|
|
forum.LastTopicTime = time
|
|
|
|
return nil
|
|
}
|
|
|
|
func (mfs *MemoryForumStore) Create(forumName string, forumDesc string, active bool, preset string) (int, error) {
|
|
forumCreateMutex.Lock()
|
|
res, err := createForumStmt.Exec(forumName, forumDesc, active, preset)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
fid64, err := res.LastInsertId()
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
fid := int(fid64)
|
|
|
|
mfs.forums.Store(fid, &Forum{fid, buildForumURL(nameToSlug(forumName), fid), forumName, forumDesc, active, preset, 0, "", 0, "", "", 0, "", 0, ""})
|
|
mfs.forumCount++
|
|
|
|
// TODO: Add a GroupStore. How would it interact with the ForumStore?
|
|
permmapToQuery(presetToPermmap(preset), fid)
|
|
forumCreateMutex.Unlock()
|
|
|
|
if active {
|
|
mfs.rebuildView()
|
|
}
|
|
return fid, nil
|
|
}
|
|
|
|
// TODO: Get the total count of forums in the forum store minus the blanked forums rather than doing a heavy query for this?
|
|
// GetGlobalCount returns the total number of forums
|
|
func (mfs *MemoryForumStore) GetGlobalCount() (fcount int) {
|
|
err := mfs.getForumCount.QueryRow().Scan(&fcount)
|
|
if err != nil {
|
|
LogError(err)
|
|
}
|
|
return fcount
|
|
}
|
|
|
|
// TODO: Work on SqlForumStore
|