330 lines
10 KiB
Go
330 lines
10 KiB
Go
package common
|
|
|
|
import (
|
|
"strconv"
|
|
"sync"
|
|
|
|
"../query_gen/lib"
|
|
)
|
|
|
|
var TopicList TopicListInt
|
|
|
|
type TopicListHolder struct {
|
|
List []*TopicsRow
|
|
ForumList []Forum
|
|
PageList []int
|
|
Page int
|
|
LastPage int
|
|
}
|
|
|
|
type TopicListInt interface {
|
|
GetListByGroup(group *Group, page int) (topicList []*TopicsRow, forumList []Forum, pageList []int, outPage int, lastPage int, err error)
|
|
GetList(page int) (topicList []*TopicsRow, forumList []Forum, pageList []int, outPage int, lastPage int, err error)
|
|
}
|
|
|
|
type DefaultTopicList struct {
|
|
oddGroups map[int]*TopicListHolder
|
|
evenGroups map[int]*TopicListHolder
|
|
oddLock sync.RWMutex
|
|
evenLock sync.RWMutex
|
|
|
|
groupList []int // TODO: Use an atomic.Value instead to allow this to be updated on long ticks
|
|
}
|
|
|
|
func NewDefaultTopicList() (*DefaultTopicList, error) {
|
|
tList := &DefaultTopicList{
|
|
oddGroups: make(map[int]*TopicListHolder),
|
|
evenGroups: make(map[int]*TopicListHolder),
|
|
}
|
|
|
|
var slots = make([]int, 8) // Only cache the topic list for eight groups
|
|
|
|
// TODO: Do something more efficient than this
|
|
allGroups, err := Groups.GetAll()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if len(allGroups) > 0 {
|
|
var stopHere int
|
|
if len(allGroups) <= 8 {
|
|
stopHere = len(allGroups)
|
|
} else {
|
|
stopHere = 8
|
|
}
|
|
|
|
var lowest = allGroups[0].UserCount
|
|
for i := 0; i < stopHere; i++ {
|
|
slots[i] = i
|
|
if allGroups[i].UserCount < lowest {
|
|
lowest = allGroups[i].UserCount
|
|
}
|
|
}
|
|
|
|
var findNewLowest = func() {
|
|
for _, slot := range slots {
|
|
if allGroups[slot].UserCount < lowest {
|
|
lowest = allGroups[slot].UserCount
|
|
}
|
|
}
|
|
}
|
|
|
|
for i := 8; i < len(allGroups); i++ {
|
|
if allGroups[i].UserCount > lowest {
|
|
for ii, slot := range slots {
|
|
if allGroups[i].UserCount > slot {
|
|
slots[ii] = i
|
|
lowest = allGroups[i].UserCount
|
|
findNewLowest()
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
tList.groupList = slots
|
|
}
|
|
|
|
err = tList.Tick()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
AddScheduledHalfSecondTask(tList.Tick)
|
|
//AddScheduledSecondTask(tList.GroupCountTick) // TODO: Dynamically change the groups in the short list to be optimised every second
|
|
return tList, nil
|
|
}
|
|
|
|
// TODO: Add support for groups other than the guest group
|
|
func (tList *DefaultTopicList) Tick() error {
|
|
var oddLists = make(map[int]*TopicListHolder)
|
|
var evenLists = make(map[int]*TopicListHolder)
|
|
|
|
var addList = func(gid int, topicList []*TopicsRow, forumList []Forum, pageList []int, page int, lastPage int) {
|
|
if gid%2 == 0 {
|
|
evenLists[gid] = &TopicListHolder{topicList, forumList, pageList, page, lastPage}
|
|
} else {
|
|
oddLists[gid] = &TopicListHolder{topicList, forumList, pageList, page, lastPage}
|
|
}
|
|
}
|
|
|
|
guestGroup, err := Groups.Get(GuestUser.Group)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
topicList, forumList, pageList, page, lastPage, err := tList.getListByGroup(guestGroup, 1)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
addList(guestGroup.ID, topicList, forumList, pageList, page, lastPage)
|
|
|
|
for _, gid := range tList.groupList {
|
|
group, err := Groups.Get(gid) // TODO: Bulk load the groups?
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if group.UserCount == 0 {
|
|
continue
|
|
}
|
|
|
|
topicList, forumList, pageList, page, lastPage, err := tList.getListByGroup(group, 1)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
addList(group.ID, topicList, forumList, pageList, page, lastPage)
|
|
}
|
|
|
|
tList.oddLock.Lock()
|
|
tList.oddGroups = oddLists
|
|
tList.oddLock.Unlock()
|
|
|
|
tList.evenLock.Lock()
|
|
tList.evenGroups = evenLists
|
|
tList.evenLock.Unlock()
|
|
|
|
return nil
|
|
}
|
|
|
|
func (tList *DefaultTopicList) GetListByGroup(group *Group, page int) (topicList []*TopicsRow, forumList []Forum, pageList []int, outPage int, lastPage int, err error) {
|
|
// TODO: Cache the first three pages not just the first along with all the topics on this beaten track
|
|
if page == 1 {
|
|
var holder *TopicListHolder
|
|
var ok bool
|
|
if group.ID%2 == 0 {
|
|
tList.evenLock.RLock()
|
|
holder, ok = tList.evenGroups[group.ID]
|
|
tList.evenLock.RUnlock()
|
|
} else {
|
|
tList.oddLock.RLock()
|
|
holder, ok = tList.oddGroups[group.ID]
|
|
tList.oddLock.RUnlock()
|
|
}
|
|
if ok {
|
|
return holder.List, holder.ForumList, holder.PageList, holder.Page, holder.LastPage, nil
|
|
}
|
|
}
|
|
|
|
return tList.getListByGroup(group, page)
|
|
}
|
|
|
|
func (tList *DefaultTopicList) getListByGroup(group *Group, page int) (topicList []*TopicsRow, forumList []Forum, pageList []int, outPage int, lastPage int, err error) {
|
|
// TODO: Make CanSee a method on *Group with a canSee field? Have a CanSee method on *User to cover the case of superadmins?
|
|
canSee := group.CanSee
|
|
|
|
// We need a list of the visible forums for Quick Topic
|
|
// ? - Would it be useful, if we could post in social groups from /topics/?
|
|
for _, fid := range canSee {
|
|
forum := Forums.DirtyGet(fid)
|
|
if forum.Name != "" && forum.Active && (forum.ParentType == "" || forum.ParentType == "forum") {
|
|
fcopy := forum.Copy()
|
|
// TODO: Add a hook here for plugin_guilds
|
|
forumList = append(forumList, fcopy)
|
|
}
|
|
}
|
|
|
|
// ? - Should we be showing plugin_guilds posts on /topics/?
|
|
argList, qlist := ForumListToArgQ(forumList)
|
|
if qlist == "" {
|
|
// We don't want to kill the page, so pass an empty slice and nil error
|
|
return topicList, forumList, pageList, 1, 1, nil
|
|
}
|
|
|
|
topicList, pageList, page, lastPage, err = tList.getList(page, argList, qlist)
|
|
return topicList, forumList, pageList, page, lastPage, err
|
|
}
|
|
|
|
// TODO: Reduce the number of returns
|
|
func (tList *DefaultTopicList) GetList(page int) (topicList []*TopicsRow, forumList []Forum, pageList []int, outPage int, lastPage int, err error) {
|
|
// TODO: Make CanSee a method on *Group with a canSee field? Have a CanSee method on *User to cover the case of superadmins?
|
|
canSee, err := Forums.GetAllVisibleIDs()
|
|
if err != nil {
|
|
return nil, nil, nil, 1, 1, err
|
|
}
|
|
|
|
// We need a list of the visible forums for Quick Topic
|
|
// ? - Would it be useful, if we could post in social groups from /topics/?
|
|
for _, fid := range canSee {
|
|
forum := Forums.DirtyGet(fid)
|
|
if forum.Name != "" && forum.Active && (forum.ParentType == "" || forum.ParentType == "forum") {
|
|
fcopy := forum.Copy()
|
|
// TODO: Add a hook here for plugin_guilds
|
|
forumList = append(forumList, fcopy)
|
|
}
|
|
}
|
|
|
|
// ? - Should we be showing plugin_guilds posts on /topics/?
|
|
argList, qlist := ForumListToArgQ(forumList)
|
|
if qlist == "" {
|
|
// If the super admin can't see anything, then things have gone terribly wrong
|
|
return topicList, forumList, pageList, 1, 1, err
|
|
}
|
|
|
|
topicList, pageList, outPage, lastPage, err = tList.getList(page, argList, qlist)
|
|
return topicList, forumList, pageList, outPage, lastPage, err
|
|
}
|
|
|
|
// TODO: Rename this to TopicListStore and pass back a TopicList instance holding the pagination data and topic list rather than passing them back one argument at a time
|
|
func (tList *DefaultTopicList) getList(page int, argList []interface{}, qlist string) (topicList []*TopicsRow, pageList []int, outPage int, lastPage int, err error) {
|
|
topicCount, err := ArgQToTopicCount(argList, qlist)
|
|
if err != nil {
|
|
return nil, nil, 1, 1, err
|
|
}
|
|
offset, page, lastPage := PageOffset(topicCount, page, Config.ItemsPerPage)
|
|
|
|
stmt, err := qgen.Builder.SimpleSelect("topics", "tid, title, content, createdBy, is_closed, sticky, createdAt, lastReplyAt, lastReplyBy, parentID, postCount, likeCount", "parentID IN("+qlist+")", "sticky DESC, lastReplyAt DESC, createdBy DESC", "?,?")
|
|
if err != nil {
|
|
return nil, nil, 1, 1, err
|
|
}
|
|
defer stmt.Close()
|
|
|
|
argList = append(argList, offset)
|
|
argList = append(argList, Config.ItemsPerPage)
|
|
|
|
rows, err := stmt.Query(argList...)
|
|
if err != nil {
|
|
return nil, nil, 1, 1, err
|
|
}
|
|
defer rows.Close()
|
|
|
|
var reqUserList = make(map[int]bool)
|
|
for rows.Next() {
|
|
topicItem := TopicsRow{ID: 0}
|
|
err := rows.Scan(&topicItem.ID, &topicItem.Title, &topicItem.Content, &topicItem.CreatedBy, &topicItem.IsClosed, &topicItem.Sticky, &topicItem.CreatedAt, &topicItem.LastReplyAt, &topicItem.LastReplyBy, &topicItem.ParentID, &topicItem.PostCount, &topicItem.LikeCount)
|
|
if err != nil {
|
|
return nil, nil, 1, 1, err
|
|
}
|
|
|
|
topicItem.Link = BuildTopicURL(NameToSlug(topicItem.Title), topicItem.ID)
|
|
forum := Forums.DirtyGet(topicItem.ParentID)
|
|
topicItem.ForumName = forum.Name
|
|
topicItem.ForumLink = forum.Link
|
|
|
|
//topicItem.CreatedAt = RelativeTime(topicItem.CreatedAt)
|
|
topicItem.RelativeLastReplyAt = RelativeTime(topicItem.LastReplyAt)
|
|
|
|
// TODO: Rename this Vhook to better reflect moving the topic list from /routes/ to /common/
|
|
RunVhook("topics_topic_row_assign", &topicItem, &forum)
|
|
topicList = append(topicList, &topicItem)
|
|
reqUserList[topicItem.CreatedBy] = true
|
|
reqUserList[topicItem.LastReplyBy] = true
|
|
}
|
|
err = rows.Err()
|
|
if err != nil {
|
|
return nil, nil, 1, 1, err
|
|
}
|
|
|
|
// Convert the user ID map to a slice, then bulk load the users
|
|
var idSlice = make([]int, len(reqUserList))
|
|
var i int
|
|
for userID := range reqUserList {
|
|
idSlice[i] = userID
|
|
i++
|
|
}
|
|
|
|
// TODO: What if a user is deleted via the Control Panel?
|
|
userList, err := Users.BulkGetMap(idSlice)
|
|
if err != nil {
|
|
return nil, nil, 1, 1, err
|
|
}
|
|
|
|
// Second pass to the add the user data
|
|
// TODO: Use a pointer to TopicsRow instead of TopicsRow itself?
|
|
for _, topicItem := range topicList {
|
|
topicItem.Creator = userList[topicItem.CreatedBy]
|
|
topicItem.LastUser = userList[topicItem.LastReplyBy]
|
|
}
|
|
|
|
pageList = Paginate(topicCount, Config.ItemsPerPage, 5)
|
|
return topicList, pageList, page, lastPage, nil
|
|
}
|
|
|
|
// Internal. Don't rely on it.
|
|
func ForumListToArgQ(forums []Forum) (argList []interface{}, qlist string) {
|
|
for _, forum := range forums {
|
|
argList = append(argList, strconv.Itoa(forum.ID))
|
|
qlist += "?,"
|
|
}
|
|
if qlist != "" {
|
|
qlist = qlist[0 : len(qlist)-1]
|
|
}
|
|
return argList, qlist
|
|
}
|
|
|
|
// Internal. Don't rely on it.
|
|
func ArgQToTopicCount(argList []interface{}, qlist string) (topicCount int, err error) {
|
|
topicCountStmt, err := qgen.Builder.SimpleCount("topics", "parentID IN("+qlist+")", "")
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
defer topicCountStmt.Close()
|
|
|
|
err = topicCountStmt.QueryRow(argList...).Scan(&topicCount)
|
|
if err != nil && err != ErrNoRows {
|
|
return 0, err
|
|
}
|
|
return topicCount, err
|
|
}
|
|
|
|
func TopicCountInForums(forums []Forum) (topicCount int, err error) {
|
|
return ArgQToTopicCount(ForumListToArgQ(forums))
|
|
}
|