The time it took for the route to be processed is shown in the footer in the Nox Theme for admins now
Don't waste queries on groups without any users for the topic list. Bypass the GzipResponseWriter in RunThemeTemplate, so we get indirected through fewer layers of Write calls. Added an experimental flag for disabling fsnotify. Refactored the topic benchmarks to make them a little more flexible. Refactored a couple of benchmarks to reduce the amount of boilerplate. Fixed a bug in the forum list for Nox where topic titles broke onto multiple lines. Refactored the template transpiler to reduce the amount of boilerplate for generating template functions.
This commit is contained in:
parent
8e997048e3
commit
17f85ceccf
@ -174,11 +174,8 @@ func PermmapToQuery(permmap map[string]*ForumPerms, fid int) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = FPStore.Reload(fid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return TopicList.RebuildPermTree()
|
||||
return FPStore.Reload(fid)
|
||||
//return TopicList.RebuildPermTree()
|
||||
}
|
||||
|
||||
// TODO: FPStore.Reload?
|
||||
@ -193,11 +190,8 @@ func ReplaceForumPermsForGroup(gid int, presetSet map[int]string, permSets map[i
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = tx.Commit()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return TopicList.RebuildPermTree()
|
||||
return tx.Commit()
|
||||
//return TopicList.RebuildPermTree()
|
||||
}
|
||||
|
||||
func ReplaceForumPermsForGroupTx(tx *sql.Tx, gid int, presetSets map[int]string, permSets map[int]*ForumPerms) error {
|
||||
|
@ -280,16 +280,8 @@ func (mgs *MemoryGroupStore) Create(name string, tag string, isAdmin bool, isMod
|
||||
mgs.groupCount++
|
||||
mgs.Unlock()
|
||||
|
||||
err = FPStore.ReloadAll()
|
||||
if err != nil {
|
||||
return gid, err
|
||||
}
|
||||
err = TopicList.RebuildPermTree()
|
||||
if err != nil {
|
||||
return gid, err
|
||||
}
|
||||
|
||||
return gid, nil
|
||||
return gid, FPStore.ReloadAll()
|
||||
//return gid, TopicList.RebuildPermTree()
|
||||
}
|
||||
|
||||
func (mgs *MemoryGroupStore) GetAll() (results []*Group, err error) {
|
||||
|
@ -29,6 +29,7 @@ type Header struct {
|
||||
Zone string
|
||||
Path string
|
||||
MetaDesc string
|
||||
StartedAt time.Time
|
||||
Writer http.ResponseWriter
|
||||
ExtData ExtData
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// nolint
|
||||
@ -115,6 +116,10 @@ func panelUserCheck(w http.ResponseWriter, r *http.Request, user *User) (header
|
||||
Writer: w,
|
||||
}
|
||||
// TODO: We should probably initialise header.ExtData
|
||||
// ? - Should we only show this in debug mode? It might be useful for detecting issues in production, if we show it there as-well
|
||||
if user.IsAdmin {
|
||||
header.StartedAt = time.Now()
|
||||
}
|
||||
|
||||
header.AddSheet(theme.Name + "/panel.css")
|
||||
if len(theme.Resources) > 0 {
|
||||
@ -193,6 +198,11 @@ func userCheck(w http.ResponseWriter, r *http.Request, user *User) (header *Head
|
||||
if user.Loggedin && !user.Active {
|
||||
header.AddNotice("account_inactive")
|
||||
}
|
||||
// An optimisation so we don't populate StartedAt for users who shouldn't see the stat anyway
|
||||
// ? - Should we only show this in debug mode? It might be useful for detecting issues in production, if we show it there as-well
|
||||
if user.IsAdmin {
|
||||
header.StartedAt = time.Now()
|
||||
}
|
||||
|
||||
if len(theme.Resources) > 0 {
|
||||
rlist := theme.Resources
|
||||
|
@ -96,6 +96,7 @@ type devConfig struct {
|
||||
TemplateDebug bool
|
||||
Profiling bool
|
||||
TestDB bool
|
||||
NoFsnotify bool // Super Experimental!
|
||||
}
|
||||
|
||||
// configHolder is purely for having a big struct to unmarshal data into
|
||||
|
@ -12,8 +12,8 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/Azareal/Gosora/common/alerts"
|
||||
"github.com/Azareal/Gosora/common/templates"
|
||||
"github.com/Azareal/Gosora/common/phrases"
|
||||
"github.com/Azareal/Gosora/common/templates"
|
||||
)
|
||||
|
||||
var Ctemplates []string
|
||||
@ -162,15 +162,14 @@ func tmplInitHeaders(user User, user2 User, user3 User) (*Header, *Header, *Head
|
||||
},
|
||||
}
|
||||
|
||||
var header2 = &Header{Site: Site}
|
||||
*header2 = *header
|
||||
header2.CurrentUser = user2
|
||||
buildHeader := func(user User) *Header {
|
||||
var head = &Header{Site: Site}
|
||||
*head = *header
|
||||
head.CurrentUser = user
|
||||
return head
|
||||
}
|
||||
|
||||
var header3 = &Header{Site: Site}
|
||||
*header3 = *header
|
||||
header3.CurrentUser = user3
|
||||
|
||||
return header, header2, header3
|
||||
return header, buildHeader(user2), buildHeader(user3)
|
||||
}
|
||||
|
||||
// ? - Add template hooks?
|
||||
@ -527,6 +526,10 @@ func InitTemplates() error {
|
||||
return template.HTML(BuildWidget(dock.(string), headerInt.(*Header)))
|
||||
}
|
||||
|
||||
fmap["elapsed"] = func(startedAtInt interface{}) interface{} {
|
||||
return time.Since(startedAtInt.(time.Time)).String()
|
||||
}
|
||||
|
||||
fmap["lang"] = func(phraseNameInt interface{}) interface{} {
|
||||
phraseName, ok := phraseNameInt.(string)
|
||||
if !ok {
|
||||
|
@ -14,6 +14,7 @@ import (
|
||||
|
||||
// TODO: Turn this file into a library
|
||||
var textOverlapList = make(map[string]int)
|
||||
|
||||
// TODO: Stop hard-coding this here
|
||||
var langPkg = "github.com/Azareal/Gosora/common/phrases"
|
||||
|
||||
@ -86,6 +87,7 @@ func NewCTemplateSet() *CTemplateSet {
|
||||
"multiply": true,
|
||||
"divide": true,
|
||||
"dock": true,
|
||||
"elapsed": true,
|
||||
"lang": true,
|
||||
"level": true,
|
||||
"scope": true,
|
||||
@ -626,6 +628,24 @@ func (c *CTemplateSet) compareJoin(varholder string, holdreflect reflect.Value,
|
||||
|
||||
func (c *CTemplateSet) compileIdentSwitch(varholder string, holdreflect reflect.Value, templateName string, node *parse.CommandNode) (out string, val reflect.Value, literal bool) {
|
||||
c.detail("in compileIdentSwitch")
|
||||
var litString = func(inner string) {
|
||||
out = "w.Write([]byte(" + inner + "))\n"
|
||||
literal = true
|
||||
}
|
||||
var compOpMappings = map[string]string{
|
||||
"le": "<=",
|
||||
"lt": "<",
|
||||
"gt": ">",
|
||||
"ge": ">=",
|
||||
"eq": "==",
|
||||
"ne": "!=",
|
||||
}
|
||||
var mathOpMappings = map[string]string{
|
||||
"add": "+",
|
||||
"subtract": "-",
|
||||
"divide": "/",
|
||||
"multiply": "*",
|
||||
}
|
||||
ArgLoop:
|
||||
for pos := 0; pos < len(node.Args); pos++ {
|
||||
id := node.Args[pos]
|
||||
@ -638,43 +658,21 @@ ArgLoop:
|
||||
var rout string
|
||||
pos, rout = c.compareJoin(varholder, holdreflect, templateName, pos, node, c.funcMap[id.String()].(string)) // TODO: Test this
|
||||
out += rout
|
||||
case "le": // TODO: Can we condense these comparison cases down into one?
|
||||
out += c.compareFunc(varholder, holdreflect, templateName, pos, node, "<=")
|
||||
case "le", "lt", "gt", "ge", "eq", "ne":
|
||||
out += c.compareFunc(varholder, holdreflect, templateName, pos, node, compOpMappings[id.String()])
|
||||
break ArgLoop
|
||||
case "lt":
|
||||
out += c.compareFunc(varholder, holdreflect, templateName, pos, node, "<")
|
||||
break ArgLoop
|
||||
case "gt":
|
||||
out += c.compareFunc(varholder, holdreflect, templateName, pos, node, ">")
|
||||
break ArgLoop
|
||||
case "ge":
|
||||
out += c.compareFunc(varholder, holdreflect, templateName, pos, node, ">=")
|
||||
break ArgLoop
|
||||
case "eq":
|
||||
out += c.compareFunc(varholder, holdreflect, templateName, pos, node, "==")
|
||||
break ArgLoop
|
||||
case "ne":
|
||||
out += c.compareFunc(varholder, holdreflect, templateName, pos, node, "!=")
|
||||
break ArgLoop
|
||||
case "add":
|
||||
rout, rval := c.simpleMath(varholder, holdreflect, templateName, pos, node, "+")
|
||||
case "add", "subtract", "divide", "multiply":
|
||||
rout, rval := c.simpleMath(varholder, holdreflect, templateName, pos, node, mathOpMappings[id.String()])
|
||||
out += rout
|
||||
val = rval
|
||||
break ArgLoop
|
||||
case "subtract":
|
||||
rout, rval := c.simpleMath(varholder, holdreflect, templateName, pos, node, "-")
|
||||
out += rout
|
||||
val = rval
|
||||
break ArgLoop
|
||||
case "divide":
|
||||
rout, rval := c.simpleMath(varholder, holdreflect, templateName, pos, node, "/")
|
||||
out += rout
|
||||
val = rval
|
||||
break ArgLoop
|
||||
case "multiply":
|
||||
rout, rval := c.simpleMath(varholder, holdreflect, templateName, pos, node, "*")
|
||||
out += rout
|
||||
val = rval
|
||||
case "elapsed":
|
||||
leftOperand := node.Args[pos+1].String()
|
||||
leftParam, _ := c.compileIfVarsub(leftOperand, varholder, templateName, holdreflect)
|
||||
// TODO: Refactor this
|
||||
// TODO: Validate that this is actually a time.Time
|
||||
litString("time.Since(" + leftParam + ").String()")
|
||||
c.importMap["time"] = "time"
|
||||
break ArgLoop
|
||||
case "dock":
|
||||
var leftParam, rightParam string
|
||||
@ -701,40 +699,33 @@ ArgLoop:
|
||||
val = val3
|
||||
|
||||
// TODO: Refactor this
|
||||
out = "w.Write([]byte(common.BuildWidget(" + leftParam + "," + rightParam + ")))\n"
|
||||
literal = true
|
||||
litString("common.BuildWidget(" + leftParam + "," + rightParam + ")")
|
||||
break ArgLoop
|
||||
case "lang":
|
||||
var leftParam string
|
||||
// TODO: Implement string literals properly
|
||||
leftOperand := node.Args[pos+1].String()
|
||||
if len(leftOperand) == 0 {
|
||||
panic("The left operand for the language string cannot be left blank")
|
||||
}
|
||||
|
||||
if leftOperand[0] == '"' {
|
||||
// ! Slightly crude but it does the job
|
||||
leftParam = strings.Replace(leftOperand, "\"", "", -1)
|
||||
} else {
|
||||
if leftOperand[0] != '"' {
|
||||
panic("Phrase names cannot be dynamic")
|
||||
}
|
||||
|
||||
// ! Slightly crude but it does the job
|
||||
leftParam := strings.Replace(leftOperand, "\"", "", -1)
|
||||
c.langIndexToName = append(c.langIndexToName, leftParam)
|
||||
out = "w.Write(plist[" + strconv.Itoa(len(c.langIndexToName)-1) + "])\n"
|
||||
literal = true
|
||||
litString("plist[" + strconv.Itoa(len(c.langIndexToName)-1) + "]")
|
||||
break ArgLoop
|
||||
case "level":
|
||||
var leftParam string
|
||||
// TODO: Implement level literals
|
||||
leftOperand := node.Args[pos+1].String()
|
||||
if len(leftOperand) == 0 {
|
||||
panic("The leftoperand for function level cannot be left blank")
|
||||
}
|
||||
|
||||
leftParam, _ = c.compileIfVarsub(leftOperand, varholder, templateName, holdreflect)
|
||||
leftParam, _ := c.compileIfVarsub(leftOperand, varholder, templateName, holdreflect)
|
||||
// TODO: Refactor this
|
||||
out = "w.Write([]byte(phrases.GetLevelPhrase(" + leftParam + ")))\n"
|
||||
literal = true
|
||||
litString("phrases.GetLevelPhrase(" + leftParam + ")")
|
||||
c.importMap[langPkg] = langPkg
|
||||
break ArgLoop
|
||||
case "scope":
|
||||
|
@ -289,10 +289,25 @@ func ResetTemplateOverrides() {
|
||||
log.Print("All of the template overrides have been reset")
|
||||
}
|
||||
|
||||
type GzipResponseWriter struct {
|
||||
io.Writer
|
||||
http.ResponseWriter
|
||||
}
|
||||
|
||||
func (w GzipResponseWriter) Write(b []byte) (int, error) {
|
||||
return w.Writer.Write(b)
|
||||
}
|
||||
|
||||
// NEW method of doing theme templates to allow one user to have a different theme to another. Under construction.
|
||||
// TODO: Generate the type switch instead of writing it by hand
|
||||
// TODO: Cut the number of types in half
|
||||
func RunThemeTemplate(theme string, template string, pi interface{}, w io.Writer) error {
|
||||
// Unpack this to avoid an indirect call
|
||||
gzw, ok := w.(GzipResponseWriter)
|
||||
if ok {
|
||||
w = gzw.Writer
|
||||
}
|
||||
|
||||
var getTmpl = GetThemeTemplate(theme, template)
|
||||
switch tmplO := getTmpl.(type) {
|
||||
case *func(CustomPagePage, io.Writer) error:
|
||||
|
@ -3,7 +3,6 @@ package common
|
||||
import (
|
||||
"strconv"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/Azareal/Gosora/query_gen"
|
||||
)
|
||||
@ -20,8 +19,6 @@ type TopicListInt interface {
|
||||
GetListByCanSee(canSee []int, page int, orderby string) (topicList []*TopicsRow, forumList []Forum, paginator Paginator, err error)
|
||||
GetListByGroup(group *Group, page int, orderby string) (topicList []*TopicsRow, forumList []Forum, paginator Paginator, err error)
|
||||
GetList(page int, orderby string) (topicList []*TopicsRow, forumList []Forum, paginator Paginator, err error)
|
||||
|
||||
RebuildPermTree() error
|
||||
}
|
||||
|
||||
type DefaultTopicList struct {
|
||||
@ -31,7 +28,7 @@ type DefaultTopicList struct {
|
||||
oddLock sync.RWMutex
|
||||
evenLock sync.RWMutex
|
||||
|
||||
permTree atomic.Value // [string(canSee)]canSee
|
||||
//permTree atomic.Value // [string(canSee)]canSee
|
||||
//permTree map[string][]int // [string(canSee)]canSee
|
||||
}
|
||||
|
||||
@ -44,11 +41,7 @@ func NewDefaultTopicList() (*DefaultTopicList, error) {
|
||||
evenGroups: make(map[int]*TopicListHolder),
|
||||
}
|
||||
|
||||
err := tList.RebuildPermTree()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = tList.Tick()
|
||||
err := tList.Tick()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -70,19 +63,13 @@ func (tList *DefaultTopicList) Tick() error {
|
||||
}
|
||||
}
|
||||
|
||||
var canSeeHolders = make(map[string]*TopicListHolder)
|
||||
for name, canSee := range tList.permTree.Load().(map[string][]int) {
|
||||
topicList, forumList, paginator, err := tList.GetListByCanSee(canSee, 1, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
canSeeHolders[name] = &TopicListHolder{topicList, forumList, paginator}
|
||||
}
|
||||
|
||||
allGroups, err := Groups.GetAll()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var gidToCanSee = make(map[int]string)
|
||||
var permTree = make(map[string][]int) // [string(canSee)]canSee
|
||||
for _, group := range allGroups {
|
||||
// ? - Move the user count check to instance initialisation? Might require more book-keeping, particularly when a user moves into a zero user group
|
||||
if group.UserCount == 0 && group.ID != GuestUser.Group {
|
||||
@ -92,7 +79,23 @@ func (tList *DefaultTopicList) Tick() error {
|
||||
for i, item := range group.CanSee {
|
||||
canSee[i] = byte(item)
|
||||
}
|
||||
addList(group.ID, canSeeHolders[string(canSee)])
|
||||
var canSeeInt = make([]int, len(canSee))
|
||||
copy(canSeeInt, group.CanSee)
|
||||
sCanSee := string(canSee)
|
||||
permTree[sCanSee] = canSeeInt
|
||||
gidToCanSee[group.ID] = sCanSee
|
||||
}
|
||||
|
||||
var canSeeHolders = make(map[string]*TopicListHolder)
|
||||
for name, canSee := range permTree {
|
||||
topicList, forumList, paginator, err := tList.GetListByCanSee(canSee, 1, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
canSeeHolders[name] = &TopicListHolder{topicList, forumList, paginator}
|
||||
}
|
||||
for gid, canSee := range gidToCanSee {
|
||||
addList(gid, canSeeHolders[canSee])
|
||||
}
|
||||
|
||||
tList.oddLock.Lock()
|
||||
@ -106,28 +109,6 @@ func (tList *DefaultTopicList) Tick() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (tList *DefaultTopicList) RebuildPermTree() error {
|
||||
// TODO: Do something more efficient than this
|
||||
allGroups, err := Groups.GetAll()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var permTree = make(map[string][]int) // [string(canSee)]canSee
|
||||
for _, group := range allGroups {
|
||||
var canSee = make([]byte, len(group.CanSee))
|
||||
for i, item := range group.CanSee {
|
||||
canSee[i] = byte(item)
|
||||
}
|
||||
var canSeeInt = make([]int, len(canSee))
|
||||
copy(canSeeInt, group.CanSee)
|
||||
permTree[string(canSee)] = canSeeInt
|
||||
}
|
||||
tList.permTree.Store(permTree)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (tList *DefaultTopicList) GetListByGroup(group *Group, page int, orderby string) (topicList []*TopicsRow, forumList []Forum, paginator Paginator, err error) {
|
||||
if page == 0 {
|
||||
page = 1
|
||||
|
@ -10,7 +10,6 @@ import (
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
"net/http"
|
||||
|
||||
@ -536,15 +535,6 @@ func init() {
|
||||
counters.SetReverseOSMapEnum(reverseOSMapEnum)
|
||||
}
|
||||
|
||||
type gzipResponseWriter struct {
|
||||
io.Writer
|
||||
http.ResponseWriter
|
||||
}
|
||||
|
||||
func (w gzipResponseWriter) Write(b []byte) (int, error) {
|
||||
return w.Writer.Write(b)
|
||||
}
|
||||
|
||||
type WriterIntercept struct {
|
||||
http.ResponseWriter
|
||||
}
|
||||
@ -889,7 +879,7 @@ func (r *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
gz.Close()
|
||||
}
|
||||
}()
|
||||
w = gzipResponseWriter{Writer: gz, ResponseWriter: w}
|
||||
w = common.GzipResponseWriter{Writer: gz, ResponseWriter: w}
|
||||
}
|
||||
|
||||
ferr := r.routeSwitch(w, req, user, prefix, extraData)
|
||||
@ -944,7 +934,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
|
||||
return err
|
||||
}
|
||||
|
||||
gzw, ok := w.(gzipResponseWriter)
|
||||
gzw, ok := w.(common.GzipResponseWriter)
|
||||
if ok {
|
||||
w = gzw.ResponseWriter
|
||||
w.Header().Del("Content-Type")
|
||||
@ -1406,7 +1396,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
|
||||
return err
|
||||
}
|
||||
|
||||
gzw, ok := w.(gzipResponseWriter)
|
||||
gzw, ok := w.(common.GzipResponseWriter)
|
||||
if ok {
|
||||
w = gzw.ResponseWriter
|
||||
w.Header().Del("Content-Type")
|
||||
@ -1982,7 +1972,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
|
||||
if extraData == "" {
|
||||
return common.NotFound(w,req,nil)
|
||||
}
|
||||
gzw, ok := w.(gzipResponseWriter)
|
||||
gzw, ok := w.(common.GzipResponseWriter)
|
||||
if ok {
|
||||
w = gzw.ResponseWriter
|
||||
w.Header().Del("Content-Type")
|
||||
|
@ -108,6 +108,8 @@ func init() {
|
||||
}
|
||||
}
|
||||
|
||||
const benchTid = "1"
|
||||
|
||||
// TODO: Swap out LocalError for a panic for this?
|
||||
func BenchmarkTopicAdminRouteParallel(b *testing.B) {
|
||||
binit(b)
|
||||
@ -128,7 +130,7 @@ func BenchmarkTopicAdminRouteParallel(b *testing.B) {
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
w := httptest.NewRecorder()
|
||||
reqAdmin := httptest.NewRequest("get", "/topic/hm.1", bytes.NewReader(nil))
|
||||
reqAdmin := httptest.NewRequest("get", "/topic/hm."+benchTid, bytes.NewReader(nil))
|
||||
reqAdmin.AddCookie(&adminUIDCookie)
|
||||
reqAdmin.AddCookie(&adminSessionCookie)
|
||||
|
||||
@ -176,7 +178,7 @@ func BenchmarkTopicAdminRouteParallelWithRouter(b *testing.B) {
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
w := httptest.NewRecorder()
|
||||
reqAdmin := httptest.NewRequest("get", "/topic/hm.1", bytes.NewReader(nil))
|
||||
reqAdmin := httptest.NewRequest("get", "/topic/hm."+benchTid, bytes.NewReader(nil))
|
||||
reqAdmin.AddCookie(&uidCookie)
|
||||
reqAdmin.AddCookie(&sessionCookie)
|
||||
reqAdmin.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36")
|
||||
@ -215,7 +217,7 @@ func BenchmarkTopicGuestRouteParallel(b *testing.B) {
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
w := httptest.NewRecorder()
|
||||
req := httptest.NewRequest("get", "/topic/hm.1", bytes.NewReader(nil))
|
||||
req := httptest.NewRequest("get", "/topic/hm."+benchTid, bytes.NewReader(nil))
|
||||
user := common.GuestUser
|
||||
|
||||
head, err := common.UserCheck(w, req, &user)
|
||||
@ -242,7 +244,7 @@ func BenchmarkTopicGuestRouteParallelDebugMode(b *testing.B) {
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
w := httptest.NewRecorder()
|
||||
req := httptest.NewRequest("get", "/topic/hm.1", bytes.NewReader(nil))
|
||||
req := httptest.NewRequest("get", "/topic/hm."+benchTid, bytes.NewReader(nil))
|
||||
user := common.GuestUser
|
||||
|
||||
head, err := common.UserCheck(w, req, &user)
|
||||
@ -260,65 +262,6 @@ func BenchmarkTopicGuestRouteParallelDebugMode(b *testing.B) {
|
||||
cfg.Restore()
|
||||
}
|
||||
|
||||
func BenchmarkTopicGuestRouteParallelWithRouter(b *testing.B) {
|
||||
binit(b)
|
||||
router, err := NewGenRouter(http.FileServer(http.Dir("./uploads")))
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
cfg := NewStashConfig()
|
||||
common.Dev.DebugMode = false
|
||||
common.Dev.SuperDebug = false
|
||||
|
||||
/*f, err := os.Create("BenchmarkTopicGuestRouteParallelWithRouter.prof")
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
pprof.StartCPUProfile(f)*/
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
w := httptest.NewRecorder()
|
||||
req := httptest.NewRequest("GET", "/topic/hm.1", bytes.NewReader(nil))
|
||||
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36")
|
||||
req.Header.Set("Host", "localhost")
|
||||
req.Host = "localhost"
|
||||
//w.Body.Reset()
|
||||
router.ServeHTTP(w, req)
|
||||
if w.Code != 200 {
|
||||
b.Log(w.Body)
|
||||
b.Fatal("HTTP Error!")
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
//defer pprof.StopCPUProfile()
|
||||
cfg.Restore()
|
||||
}
|
||||
|
||||
func BenchmarkBadRouteGuestRouteParallelWithRouter(b *testing.B) {
|
||||
binit(b)
|
||||
router, err := NewGenRouter(http.FileServer(http.Dir("./uploads")))
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
cfg := NewStashConfig()
|
||||
common.Dev.DebugMode = false
|
||||
common.Dev.SuperDebug = false
|
||||
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
w := httptest.NewRecorder()
|
||||
req := httptest.NewRequest("GET", "/garble/haa", bytes.NewReader(nil))
|
||||
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36")
|
||||
req.Header.Set("Host", "localhost")
|
||||
req.Host = "localhost"
|
||||
router.ServeHTTP(w, req)
|
||||
}
|
||||
})
|
||||
cfg.Restore()
|
||||
}
|
||||
|
||||
func obRoute(b *testing.B, path string) {
|
||||
binit(b)
|
||||
cfg := NewStashConfig()
|
||||
@ -340,6 +283,14 @@ func BenchmarkForumGuestRouteParallelWithRouter(b *testing.B) {
|
||||
obRoute(b, "/forum/general.2")
|
||||
}
|
||||
|
||||
func BenchmarkTopicGuestRouteParallelWithRouter(b *testing.B) {
|
||||
obRoute(b, "/topic/hm."+benchTid)
|
||||
}
|
||||
|
||||
func BenchmarkBadRouteGuestRouteParallelWithRouter(b *testing.B) {
|
||||
obRoute(b, "/garble/haa")
|
||||
}
|
||||
|
||||
func binit(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
err := gloinit()
|
||||
|
142
main.go
142
main.go
@ -16,6 +16,7 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"runtime"
|
||||
"runtime/pprof"
|
||||
"strings"
|
||||
"syscall"
|
||||
@ -24,8 +25,8 @@ import (
|
||||
"github.com/Azareal/Gosora/common"
|
||||
"github.com/Azareal/Gosora/common/counters"
|
||||
"github.com/Azareal/Gosora/common/phrases"
|
||||
"github.com/Azareal/Gosora/routes"
|
||||
"github.com/Azareal/Gosora/query_gen"
|
||||
"github.com/Azareal/Gosora/routes"
|
||||
"github.com/fsnotify/fsnotify"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
@ -289,72 +290,74 @@ func main() {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
log.Print("Initialising the file watcher")
|
||||
watcher, err := fsnotify.NewWatcher()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer watcher.Close()
|
||||
|
||||
go func() {
|
||||
var modifiedFileEvent = func(path string) error {
|
||||
var pathBits = strings.Split(path, "\\")
|
||||
if len(pathBits) == 0 {
|
||||
return nil
|
||||
}
|
||||
if pathBits[0] == "themes" {
|
||||
var themeName string
|
||||
if len(pathBits) >= 2 {
|
||||
themeName = pathBits[1]
|
||||
}
|
||||
if len(pathBits) >= 3 && pathBits[2] == "public" {
|
||||
// TODO: Handle new themes freshly plopped into the folder?
|
||||
theme, ok := common.Themes[themeName]
|
||||
if ok {
|
||||
return theme.LoadStaticFiles()
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO: Expand this to more types of files
|
||||
var err error
|
||||
for {
|
||||
select {
|
||||
case event := <-watcher.Events:
|
||||
log.Println("event:", event)
|
||||
// TODO: Handle file deletes (and renames more graciously by removing the old version of it)
|
||||
if event.Op&fsnotify.Write == fsnotify.Write {
|
||||
log.Println("modified file:", event.Name)
|
||||
err = modifiedFileEvent(event.Name)
|
||||
} else if event.Op&fsnotify.Create == fsnotify.Create {
|
||||
log.Println("new file:", event.Name)
|
||||
err = modifiedFileEvent(event.Name)
|
||||
}
|
||||
if err != nil {
|
||||
common.LogError(err)
|
||||
}
|
||||
case err = <-watcher.Errors:
|
||||
common.LogError(err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// TODO: Keep tabs on the (non-resource) theme stuff, and the langpacks
|
||||
err = watcher.Add("./public")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
err = watcher.Add("./templates")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
for _, theme := range common.Themes {
|
||||
err = watcher.Add("./themes/" + theme.Name + "/public")
|
||||
if !common.Dev.NoFsnotify {
|
||||
log.Print("Initialising the file watcher")
|
||||
watcher, err := fsnotify.NewWatcher()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer watcher.Close()
|
||||
|
||||
go func() {
|
||||
var modifiedFileEvent = func(path string) error {
|
||||
var pathBits = strings.Split(path, "\\")
|
||||
if len(pathBits) == 0 {
|
||||
return nil
|
||||
}
|
||||
if pathBits[0] == "themes" {
|
||||
var themeName string
|
||||
if len(pathBits) >= 2 {
|
||||
themeName = pathBits[1]
|
||||
}
|
||||
if len(pathBits) >= 3 && pathBits[2] == "public" {
|
||||
// TODO: Handle new themes freshly plopped into the folder?
|
||||
theme, ok := common.Themes[themeName]
|
||||
if ok {
|
||||
return theme.LoadStaticFiles()
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO: Expand this to more types of files
|
||||
var err error
|
||||
for {
|
||||
select {
|
||||
case event := <-watcher.Events:
|
||||
log.Println("event:", event)
|
||||
// TODO: Handle file deletes (and renames more graciously by removing the old version of it)
|
||||
if event.Op&fsnotify.Write == fsnotify.Write {
|
||||
log.Println("modified file:", event.Name)
|
||||
err = modifiedFileEvent(event.Name)
|
||||
} else if event.Op&fsnotify.Create == fsnotify.Create {
|
||||
log.Println("new file:", event.Name)
|
||||
err = modifiedFileEvent(event.Name)
|
||||
}
|
||||
if err != nil {
|
||||
common.LogError(err)
|
||||
}
|
||||
case err = <-watcher.Errors:
|
||||
common.LogError(err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// TODO: Keep tabs on the (non-resource) theme stuff, and the langpacks
|
||||
err = watcher.Add("./public")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
err = watcher.Add("./templates")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
for _, theme := range common.Themes {
|
||||
err = watcher.Add("./themes/" + theme.Name + "/public")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.Print("Initialising the task system")
|
||||
@ -437,6 +440,17 @@ func main() {
|
||||
args := <-common.StopServerChan
|
||||
if false {
|
||||
pprof.StopCPUProfile()
|
||||
f, err := os.Create("./logs/mem.prof")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
runtime.GC()
|
||||
err = pprof.WriteHeapProfile(f)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
// Why did the server stop?
|
||||
log.Fatal(args...)
|
||||
|
@ -239,7 +239,6 @@ import (
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
"net/http"
|
||||
|
||||
@ -318,15 +317,6 @@ func init() {
|
||||
counters.SetReverseOSMapEnum(reverseOSMapEnum)
|
||||
}
|
||||
|
||||
type gzipResponseWriter struct {
|
||||
io.Writer
|
||||
http.ResponseWriter
|
||||
}
|
||||
|
||||
func (w gzipResponseWriter) Write(b []byte) (int, error) {
|
||||
return w.Writer.Write(b)
|
||||
}
|
||||
|
||||
type WriterIntercept struct {
|
||||
http.ResponseWriter
|
||||
}
|
||||
@ -671,7 +661,7 @@ func (r *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
gz.Close()
|
||||
}
|
||||
}()
|
||||
w = gzipResponseWriter{Writer: gz, ResponseWriter: w}
|
||||
w = common.GzipResponseWriter{Writer: gz, ResponseWriter: w}
|
||||
}
|
||||
|
||||
ferr := r.routeSwitch(w, req, user, prefix, extraData)
|
||||
@ -691,7 +681,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user c
|
||||
if extraData == "" {
|
||||
return common.NotFound(w,req,nil)
|
||||
}
|
||||
gzw, ok := w.(gzipResponseWriter)
|
||||
gzw, ok := w.(common.GzipResponseWriter)
|
||||
if ok {
|
||||
w = gzw.ResponseWriter
|
||||
w.Header().Del("Content-Type")
|
||||
|
@ -60,7 +60,7 @@ func (route *RouteImpl) hasBeforeItem(item string) bool {
|
||||
}
|
||||
|
||||
func (route *RouteImpl) NoGzip() *RouteImpl {
|
||||
return route.LitBeforeMultiline(`gzw, ok := w.(gzipResponseWriter)
|
||||
return route.LitBeforeMultiline(`gzw, ok := w.(common.GzipResponseWriter)
|
||||
if ok {
|
||||
w = gzw.ResponseWriter
|
||||
w.Header().Del("Content-Type")
|
||||
|
@ -9,6 +9,7 @@
|
||||
<div id="poweredBy">
|
||||
<a id="poweredByName" href="https://github.com/Azareal/Gosora">{{lang "footer_powered_by"}}</a><span id="poweredByDash"> - </span><span id="poweredByMaker">{{lang "footer_made_with_love"}}</span>
|
||||
</div>
|
||||
{{if .CurrentUser.IsAdmin}}<div class="elapsed">{{elapsed .Header.StartedAt}}</div>{{end}}
|
||||
<form action="/theme/" method="post">
|
||||
<div id="themeSelector" style="float: right;">
|
||||
<select id="themeSelectorSelect" name="themeSelector" aria-label="{{lang "footer_theme_selector_aria"}}">
|
||||
|
@ -1413,6 +1413,9 @@ textarea {
|
||||
background-color: var(--element-background-color);
|
||||
display: flex;
|
||||
}
|
||||
.elapsed {
|
||||
display: none;
|
||||
}
|
||||
#poweredByHolder {
|
||||
border-bottom: 2px solid var(--element-border-color);
|
||||
}
|
||||
|
@ -588,6 +588,9 @@ button, .formbutton, .panel_right_button:not(.has_inner_button) {
|
||||
.forum_list .forum_right span {
|
||||
line-height: 19px;
|
||||
}
|
||||
.forum_list .forum_right span a {
|
||||
white-space: nowrap;
|
||||
}
|
||||
.extra_little_row_avatar {
|
||||
border-radius: 24px;
|
||||
height: 36px;
|
||||
@ -837,10 +840,21 @@ input[type=checkbox]:checked + label .sel {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.footer .widget {
|
||||
.footer .widget, .elapsed {
|
||||
padding: 12px;
|
||||
border-bottom: 1px solid #555555;
|
||||
}
|
||||
.elapsed {
|
||||
padding: 6px;
|
||||
background: rgb(82,82,82);
|
||||
border-radius: 3px;
|
||||
font-size: 13.5px;
|
||||
color: rgb(200,200,200);
|
||||
margin-top: 1px;
|
||||
margin-bottom: 4px;
|
||||
padding-bottom: 2px;
|
||||
padding-top: 3px;
|
||||
}
|
||||
#poweredByHolder {
|
||||
display: flex;
|
||||
padding-top: 12px;
|
||||
|
@ -573,6 +573,9 @@ input, select, textarea {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
.elapsed {
|
||||
display: none;
|
||||
}
|
||||
#poweredByHolder {
|
||||
background-color: var(--main-block-color);
|
||||
padding: 10px;
|
||||
|
@ -856,6 +856,9 @@ input[type=checkbox]:checked + label.poll_option_label .sel {
|
||||
top: -2px;
|
||||
}
|
||||
|
||||
.elapsed {
|
||||
display: none;
|
||||
}
|
||||
#poweredByHolder {
|
||||
border: 1px solid hsl(0, 0%, 80%);
|
||||
margin-top: 12px;
|
||||
|
Loading…
Reference in New Issue
Block a user