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:
Azareal 2018-11-17 12:36:02 +10:00
parent 8e997048e3
commit 17f85ceccf
19 changed files with 229 additions and 272 deletions

View File

@ -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 {

View File

@ -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) {

View File

@ -29,6 +29,7 @@ type Header struct {
Zone string
Path string
MetaDesc string
StartedAt time.Time
Writer http.ResponseWriter
ExtData ExtData
}

View File

@ -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

View File

@ -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

View File

@ -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 {

View File

@ -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":

View File

@ -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:

View File

@ -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

View File

@ -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")

View File

@ -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
View File

@ -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...)

View File

@ -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")

View File

@ -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")

View File

@ -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"}}">

View File

@ -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);
}

View File

@ -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;

View File

@ -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;

View File

@ -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;