diff --git a/common/forum_perms.go b/common/forum_perms.go index f5486d4d..e6107fe2 100644 --- a/common/forum_perms.go +++ b/common/forum_perms.go @@ -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 { diff --git a/common/group_store.go b/common/group_store.go index bee00cfe..582599ba 100644 --- a/common/group_store.go +++ b/common/group_store.go @@ -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) { diff --git a/common/pages.go b/common/pages.go index c771caa2..354e7472 100644 --- a/common/pages.go +++ b/common/pages.go @@ -29,6 +29,7 @@ type Header struct { Zone string Path string MetaDesc string + StartedAt time.Time Writer http.ResponseWriter ExtData ExtData } diff --git a/common/routes_common.go b/common/routes_common.go index bd524619..bd057a15 100644 --- a/common/routes_common.go +++ b/common/routes_common.go @@ -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 diff --git a/common/site.go b/common/site.go index 2258328e..5fdc7489 100644 --- a/common/site.go +++ b/common/site.go @@ -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 diff --git a/common/template_init.go b/common/template_init.go index 77f702f3..a2ee55c2 100644 --- a/common/template_init.go +++ b/common/template_init.go @@ -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 { diff --git a/common/templates/templates.go b/common/templates/templates.go index 8d547865..c8f71c71 100644 --- a/common/templates/templates.go +++ b/common/templates/templates.go @@ -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": diff --git a/common/theme_list.go b/common/theme_list.go index b527b70e..5ff499bc 100644 --- a/common/theme_list.go +++ b/common/theme_list.go @@ -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: diff --git a/common/topic_list.go b/common/topic_list.go index 0f557a4c..976ab361 100644 --- a/common/topic_list.go +++ b/common/topic_list.go @@ -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 diff --git a/gen_router.go b/gen_router.go index bbaa0727..87fa38d0 100644 --- a/gen_router.go +++ b/gen_router.go @@ -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") diff --git a/general_test.go b/general_test.go index 55bc9a38..ba627874 100644 --- a/general_test.go +++ b/general_test.go @@ -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() diff --git a/main.go b/main.go index c9697126..a58bf91e 100644 --- a/main.go +++ b/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...) diff --git a/router_gen/main.go b/router_gen/main.go index 74a63d3d..7ad282ea 100644 --- a/router_gen/main.go +++ b/router_gen/main.go @@ -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") diff --git a/router_gen/route_impl.go b/router_gen/route_impl.go index 5c885335..694dbb4f 100644 --- a/router_gen/route_impl.go +++ b/router_gen/route_impl.go @@ -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") diff --git a/templates/footer.html b/templates/footer.html index 850a4045..9d1ff504 100644 --- a/templates/footer.html +++ b/templates/footer.html @@ -9,6 +9,7 @@
+ {{if .CurrentUser.IsAdmin}}