Refactored the error handlers.

The static files for themes are now automatically reloaded without having to restart Gosora.
Added the Get and Set methods to the SFileList type.
Static files can now be updated during execution in a thread-safe way.

Removed Riot as a dependency.
Began work on making Cosora responsive like the other themes.
This commit is contained in:
Azareal 2017-12-01 02:04:29 +00:00
parent 0e9cebfa47
commit 9eae8da180
16 changed files with 183 additions and 173 deletions

View File

@ -86,7 +86,7 @@ Several important features for saving memory in the templates system may have to
# Advanced Installation # Advanced Installation
An example of running the commands directly on Windows. An example of running the commands directly on Windows. We're looking into reducing the number of commands you need to type, for instance, you could invoke the update-deps batch or shell files to install / update all of the dependencies instead of typing each `get get -u`
Linux is similar, however you might need to use cd and mv a bit more like in the shell files due to the differences in go build across platforms. Additionally, Linux doesn't require `StackExchange/wmi` or ``/x/sys/windows` Linux is similar, however you might need to use cd and mv a bit more like in the shell files due to the differences in go build across platforms. Additionally, Linux doesn't require `StackExchange/wmi` or ``/x/sys/windows`
@ -168,7 +168,7 @@ We're looking for ways to clean-up the plugin system so that all of them (except
More images in the /images/ folder. Beware though, some of them are *really* outdated. More images in the /images/ folder. Beware though, some of them are *really* outdated.
# Dependencies (a few of these like Riot aren't currently in use, but we anticipate that we'll need some sort of search engine library in the very immediate future) # Dependencies (a few of these like Rez aren't currently in use, but we anticipate that we'll need some sort of search engine library in the very immediate future)
* Go 1.9 * Go 1.9
@ -194,8 +194,6 @@ More images in the /images/ folder. Beware though, some of them are *really* out
* ithub.com/denisenkom/go-mssqldb For interfacing with MSSQL. You will be able to pick this instead of MSSQL soon. * ithub.com/denisenkom/go-mssqldb For interfacing with MSSQL. You will be able to pick this instead of MSSQL soon.
* github.com/go-ego/riot A search engine library.
* github.com/bamiaux/rez An image resizer (e.g. for spitting out thumbnails) * github.com/bamiaux/rez An image resizer (e.g. for spitting out thumbnails)
* github.com/fsnotify/fsnotify A library for watching events on the file system. * github.com/fsnotify/fsnotify A library for watching events on the file system.

View File

@ -1,7 +1,6 @@
package common package common
import "log" import "log"
import "sync" import "sync"
import "net/http" import "net/http"
import "runtime/debug" import "runtime/debug"
@ -32,10 +31,6 @@ type RouteErrorImpl struct {
handled bool handled bool
} }
/*func NewRouteError(msg string, system bool, json bool) RouteError {
return &RouteErrorImpl{msg, system, json, false}
}*/
func (err *RouteErrorImpl) Type() string { func (err *RouteErrorImpl) Type() string {
// System errors may contain sensitive information we don't want the user to see // System errors may contain sensitive information we don't want the user to see
if err.system { if err.system {
@ -80,21 +75,9 @@ func LogWarning(err error) {
// InternalError is the main function for handling internal errors, while simultaneously printing out a page for the end-user to let them know that *something* has gone wrong // InternalError is the main function for handling internal errors, while simultaneously printing out a page for the end-user to let them know that *something* has gone wrong
// ? - Add a user parameter? // ? - Add a user parameter?
func InternalError(err error, w http.ResponseWriter, r *http.Request) RouteError { func InternalError(err error, w http.ResponseWriter, r *http.Request) RouteError {
log.Print(err) pi := Page{"Internal Server Error", GuestUser, DefaultHeaderVar(), tList, "A problem has occurred in the system."}
debug.PrintStack() handleErrorTemplate(w, r, pi)
LogError(err)
// TODO: Centralise the user struct somewhere else
user := User{0, "guest", "Guest", "", 0, false, false, false, false, false, false, GuestPerms, nil, "", false, "", "", "", "", "", 0, 0, "0.0.0.0.0", 0}
pi := Page{"Internal Server Error", user, DefaultHeaderVar(), tList, "A problem has occurred in the system."}
err = Templates.ExecuteTemplate(w, "error.html", pi)
if err != nil {
log.Print(err)
}
errorBufferMutex.Lock()
defer errorBufferMutex.Unlock()
errorBuffer = append(errorBuffer, err)
log.Fatal("")
return HandledRouteError() return HandledRouteError()
} }
@ -112,26 +95,14 @@ func InternalErrorJSQ(err error, w http.ResponseWriter, r *http.Request, isJs bo
func InternalErrorJS(err error, w http.ResponseWriter, r *http.Request) RouteError { func InternalErrorJS(err error, w http.ResponseWriter, r *http.Request) RouteError {
w.WriteHeader(500) w.WriteHeader(500)
_, _ = w.Write([]byte(`{"errmsg":"A problem has occurred in the system."}`)) _, _ = w.Write([]byte(`{"errmsg":"A problem has occurred in the system."}`))
errorBufferMutex.Lock() LogError(err)
defer errorBufferMutex.Unlock()
errorBuffer = append(errorBuffer, err)
log.Fatal(err)
return HandledRouteError() return HandledRouteError()
} }
func PreError(errmsg string, w http.ResponseWriter, r *http.Request) RouteError { func PreError(errmsg string, w http.ResponseWriter, r *http.Request) RouteError {
w.WriteHeader(500) w.WriteHeader(500)
user := User{ID: 0, Group: 6, Perms: GuestPerms} pi := Page{"Error", GuestUser, DefaultHeaderVar(), tList, errmsg}
pi := Page{"Error", user, DefaultHeaderVar(), tList, errmsg} handleErrorTemplate(w, r, pi)
if PreRenderHooks["pre_render_error"] != nil {
if RunPreRenderHook("pre_render_error", w, r, &user, &pi) {
return nil
}
}
err := Templates.ExecuteTemplate(w, "error.html", pi)
if err != nil {
LogError(err)
}
return HandledRouteError() return HandledRouteError()
} }
@ -152,15 +123,7 @@ func PreErrorJSQ(errmsg string, w http.ResponseWriter, r *http.Request, isJs boo
func LocalError(errmsg string, w http.ResponseWriter, r *http.Request, user User) RouteError { func LocalError(errmsg string, w http.ResponseWriter, r *http.Request, user User) RouteError {
w.WriteHeader(500) w.WriteHeader(500)
pi := Page{"Local Error", user, DefaultHeaderVar(), tList, errmsg} pi := Page{"Local Error", user, DefaultHeaderVar(), tList, errmsg}
if PreRenderHooks["pre_render_error"] != nil { handleErrorTemplate(w, r, pi)
if RunPreRenderHook("pre_render_error", w, r, &user, &pi) {
return nil
}
}
err := Templates.ExecuteTemplate(w, "error.html", pi)
if err != nil {
LogError(err)
}
return HandledRouteError() return HandledRouteError()
} }
@ -182,16 +145,7 @@ func LocalErrorJS(errmsg string, w http.ResponseWriter, r *http.Request) RouteEr
func NoPermissions(w http.ResponseWriter, r *http.Request, user User) RouteError { func NoPermissions(w http.ResponseWriter, r *http.Request, user User) RouteError {
w.WriteHeader(403) w.WriteHeader(403)
pi := Page{"Local Error", user, DefaultHeaderVar(), tList, "You don't have permission to do that."} pi := Page{"Local Error", user, DefaultHeaderVar(), tList, "You don't have permission to do that."}
// TODO: What to do about this hook? handleErrorTemplate(w, r, pi)
if PreRenderHooks["pre_render_error"] != nil {
if RunPreRenderHook("pre_render_error", w, r, &user, &pi) {
return nil
}
}
err := Templates.ExecuteTemplate(w, "error.html", pi)
if err != nil {
LogError(err)
}
return HandledRouteError() return HandledRouteError()
} }
@ -212,15 +166,7 @@ func NoPermissionsJS(w http.ResponseWriter, r *http.Request, user User) RouteErr
func Banned(w http.ResponseWriter, r *http.Request, user User) RouteError { func Banned(w http.ResponseWriter, r *http.Request, user User) RouteError {
w.WriteHeader(403) w.WriteHeader(403)
pi := Page{"Banned", user, DefaultHeaderVar(), tList, "You have been banned from this site."} pi := Page{"Banned", user, DefaultHeaderVar(), tList, "You have been banned from this site."}
if PreRenderHooks["pre_render_error"] != nil { handleErrorTemplate(w, r, pi)
if RunPreRenderHook("pre_render_error", w, r, &user, &pi) {
return nil
}
}
err := Templates.ExecuteTemplate(w, "error.html", pi)
if err != nil {
LogError(err)
}
return HandledRouteError() return HandledRouteError()
} }
@ -252,15 +198,7 @@ func LoginRequiredJSQ(w http.ResponseWriter, r *http.Request, user User, isJs bo
func LoginRequired(w http.ResponseWriter, r *http.Request, user User) RouteError { func LoginRequired(w http.ResponseWriter, r *http.Request, user User) RouteError {
w.WriteHeader(401) w.WriteHeader(401)
pi := Page{"Local Error", user, DefaultHeaderVar(), tList, "You need to login to do that."} pi := Page{"Local Error", user, DefaultHeaderVar(), tList, "You need to login to do that."}
if PreRenderHooks["pre_render_error"] != nil { handleErrorTemplate(w, r, pi)
if RunPreRenderHook("pre_render_error", w, r, &user, &pi) {
return nil
}
}
err := Templates.ExecuteTemplate(w, "error.html", pi)
if err != nil {
LogError(err)
}
return HandledRouteError() return HandledRouteError()
} }
@ -292,30 +230,14 @@ func SecurityError(w http.ResponseWriter, r *http.Request, user User) RouteError
// ? - Add a JSQ and JS version of this? // ? - Add a JSQ and JS version of this?
// ? - Add a user parameter? // ? - Add a user parameter?
func NotFound(w http.ResponseWriter, r *http.Request) RouteError { func NotFound(w http.ResponseWriter, r *http.Request) RouteError {
w.WriteHeader(404) return CustomError("The requested page doesn't exist.", 404, "Not Found", w, r, GuestUser)
// TODO: Centralise the user struct somewhere else
user := User{0, "guest", "Guest", "", 0, false, false, false, false, false, false, GuestPerms, nil, "", false, "", "", "", "", "", 0, 0, "0.0.0.0.0", 0}
pi := Page{"Not Found", user, DefaultHeaderVar(), tList, "The requested page doesn't exist."}
err := Templates.ExecuteTemplate(w, "error.html", pi)
if err != nil {
LogError(err)
}
return HandledRouteError()
} }
// CustomError lets us make custom error types which aren't covered by the generic functions above // CustomError lets us make custom error types which aren't covered by the generic functions above
func CustomError(errmsg string, errcode int, errtitle string, w http.ResponseWriter, r *http.Request, user User) RouteError { func CustomError(errmsg string, errcode int, errtitle string, w http.ResponseWriter, r *http.Request, user User) RouteError {
w.WriteHeader(errcode) w.WriteHeader(errcode)
pi := Page{errtitle, user, DefaultHeaderVar(), tList, errmsg} pi := Page{errtitle, user, DefaultHeaderVar(), tList, errmsg}
if PreRenderHooks["pre_render_error"] != nil { handleErrorTemplate(w, r, pi)
if RunPreRenderHook("pre_render_error", w, r, &user, &pi) {
return nil
}
}
err := Templates.ExecuteTemplate(w, "error.html", pi)
if err != nil {
LogError(err)
}
return HandledRouteError() return HandledRouteError()
} }
@ -324,12 +246,25 @@ func CustomErrorJSQ(errmsg string, errcode int, errtitle string, w http.Response
if !isJs { if !isJs {
return CustomError(errmsg, errcode, errtitle, w, r, user) return CustomError(errmsg, errcode, errtitle, w, r, user)
} }
return CustomErrorJS(errmsg, errcode, errtitle, w, r, user) return CustomErrorJS(errmsg, errcode, w, r, user)
} }
// CustomErrorJS is the pure JSON version of CustomError // CustomErrorJS is the pure JSON version of CustomError
func CustomErrorJS(errmsg string, errcode int, errtitle string, w http.ResponseWriter, r *http.Request, user User) RouteError { func CustomErrorJS(errmsg string, errcode int, w http.ResponseWriter, r *http.Request, user User) RouteError {
w.WriteHeader(errcode) w.WriteHeader(errcode)
_, _ = w.Write([]byte(`{"errmsg":"` + errmsg + `"}`)) _, _ = w.Write([]byte(`{"errmsg":"` + errmsg + `"}`))
return HandledRouteError() return HandledRouteError()
} }
func handleErrorTemplate(w http.ResponseWriter, r *http.Request, pi Page) {
// TODO: What to do about this hook?
if PreRenderHooks["pre_render_error"] != nil {
if RunPreRenderHook("pre_render_error", w, r, &pi.CurrentUser, &pi) {
return
}
}
err := Templates.ExecuteTemplate(w, "error.html", pi)
if err != nil {
LogError(err)
}
}

View File

@ -2,9 +2,9 @@ package common
import ( import (
"bytes" "bytes"
"log"
"mime" "mime"
"strings" "strings"
"sync"
//"errors" //"errors"
"compress/gzip" "compress/gzip"
"io/ioutil" "io/ioutil"
@ -16,6 +16,7 @@ import (
type SFileList map[string]SFile type SFileList map[string]SFile
var StaticFiles SFileList = make(map[string]SFile) var StaticFiles SFileList = make(map[string]SFile)
var staticFileMutex sync.RWMutex
type SFile struct { type SFile struct {
Data []byte Data []byte
@ -48,11 +49,9 @@ func (list SFileList) Init() error {
var ext = filepath.Ext("/public/" + path) var ext = filepath.Ext("/public/" + path)
gzipData := compressBytesGzip(data) gzipData := compressBytesGzip(data)
list["/static/"+path] = SFile{data, gzipData, 0, int64(len(data)), int64(len(gzipData)), mime.TypeByExtension(ext), f, f.ModTime().UTC().Format(http.TimeFormat)} list.Set("/static/"+path, SFile{data, gzipData, 0, int64(len(data)), int64(len(gzipData)), mime.TypeByExtension(ext), f, f.ModTime().UTC().Format(http.TimeFormat)})
if Dev.DebugMode { debugLogf("Added the '%s' static file.", path)
log.Print("Added the '" + path + "' static file.")
}
return nil return nil
}) })
} }
@ -75,14 +74,25 @@ func (list SFileList) Add(path string, prefix string) error {
path = strings.TrimPrefix(path, prefix) path = strings.TrimPrefix(path, prefix)
gzipData := compressBytesGzip(data) gzipData := compressBytesGzip(data)
list["/static"+path] = SFile{data, gzipData, 0, int64(len(data)), int64(len(gzipData)), mime.TypeByExtension(ext), f, f.ModTime().UTC().Format(http.TimeFormat)} list.Set("/static"+path, SFile{data, gzipData, 0, int64(len(data)), int64(len(gzipData)), mime.TypeByExtension(ext), f, f.ModTime().UTC().Format(http.TimeFormat)})
if Dev.DebugMode { debugLogf("Added the '%s' static file", path)
log.Printf("Added the '%s' static file", path)
}
return nil return nil
} }
func (list SFileList) Get(name string) (file SFile, exists bool) {
staticFileMutex.RLock()
defer staticFileMutex.RUnlock()
file, exists = list[name]
return file, exists
}
func (list SFileList) Set(name string, data SFile) {
staticFileMutex.Lock()
defer staticFileMutex.Unlock()
list[name] = data
}
func compressBytesGzip(in []byte) []byte { func compressBytesGzip(in []byte) []byte {
var buff bytes.Buffer var buff bytes.Buffer
gz := gzip.NewWriter(&buff) gz := gzip.NewWriter(&buff)

View File

@ -14,8 +14,8 @@ type HeaderVars struct {
Widgets PageWidgets Widgets PageWidgets
Site *site Site *site
Settings SettingMap Settings SettingMap
Themes map[string]Theme // TODO: Use a slice containing every theme instead of the main map for speed? Themes map[string]*Theme // TODO: Use a slice containing every theme instead of the main map for speed?
Theme Theme Theme *Theme
//TemplateName string // TODO: Use this to move template calls to the router rather than duplicating them over and over and over? //TemplateName string // TODO: Use this to move template calls to the router rather than duplicating them over and over and over?
Zone string Zone string
Writer http.ResponseWriter Writer http.ResponseWriter
@ -151,8 +151,8 @@ type PanelThemesPage struct {
CurrentUser User CurrentUser User
Header *HeaderVars Header *HeaderVars
Stats PanelStats Stats PanelStats
PrimaryThemes []Theme PrimaryThemes []*Theme
VariantThemes []Theme VariantThemes []*Theme
} }
type PanelUserPage struct { type PanelUserPage struct {

View File

@ -97,7 +97,7 @@ func cascadeForumPerms(fperms *ForumPerms, user *User) {
// Even if they have the right permissions, the control panel is only open to supermods+. There are many areas without subpermissions which assume that the current user is a supermod+ and admins are extremely unlikely to give these permissions to someone who isn't at-least a supermod to begin with // Even if they have the right permissions, the control panel is only open to supermods+. There are many areas without subpermissions which assume that the current user is a supermod+ and admins are extremely unlikely to give these permissions to someone who isn't at-least a supermod to begin with
// TODO: Do a panel specific theme? // TODO: Do a panel specific theme?
func panelUserCheck(w http.ResponseWriter, r *http.Request, user *User) (headerVars *HeaderVars, stats PanelStats, rerr RouteError) { func panelUserCheck(w http.ResponseWriter, r *http.Request, user *User) (headerVars *HeaderVars, stats PanelStats, rerr RouteError) {
var theme Theme var theme = &Theme{Name: ""}
cookie, err := r.Cookie("current_theme") cookie, err := r.Cookie("current_theme")
if err == nil { if err == nil {
@ -116,6 +116,7 @@ func panelUserCheck(w http.ResponseWriter, r *http.Request, user *User) (headerV
Themes: Themes, Themes: Themes,
Theme: theme, Theme: theme,
Zone: "panel", Zone: "panel",
Writer: w,
} }
// TODO: We should probably initialise headerVars.ExtData // TODO: We should probably initialise headerVars.ExtData
@ -189,7 +190,7 @@ func simpleUserCheck(w http.ResponseWriter, r *http.Request, user *User) (header
// TODO: Add the ability for admins to restrict certain themes to certain groups? // TODO: Add the ability for admins to restrict certain themes to certain groups?
func userCheck(w http.ResponseWriter, r *http.Request, user *User) (headerVars *HeaderVars, rerr RouteError) { func userCheck(w http.ResponseWriter, r *http.Request, user *User) (headerVars *HeaderVars, rerr RouteError) {
var theme Theme var theme = &Theme{Name: ""}
cookie, err := r.Cookie("current_theme") cookie, err := r.Cookie("current_theme")
if err == nil { if err == nil {
@ -208,6 +209,7 @@ func userCheck(w http.ResponseWriter, r *http.Request, user *User) (headerVars *
Themes: Themes, Themes: Themes,
Theme: theme, Theme: theme,
Zone: "frontend", Zone: "frontend",
Writer: w,
} }
if user.IsBanned { if user.IsBanned {

View File

@ -22,9 +22,9 @@ import (
"../query_gen/lib" "../query_gen/lib"
) )
type ThemeList map[string]Theme // ? Use pointers instead? type ThemeList map[string]*Theme
var Themes ThemeList = make(map[string]Theme) var Themes ThemeList = make(map[string]*Theme)
var DefaultThemeBox atomic.Value var DefaultThemeBox atomic.Value
var ChangeDefaultThemeMutex sync.Mutex var ChangeDefaultThemeMutex sync.Mutex
@ -45,7 +45,6 @@ type Theme struct {
Tag string Tag string
URL string URL string
Docks []string // Allowed Values: leftSidebar, rightSidebar, footer Docks []string // Allowed Values: leftSidebar, rightSidebar, footer
AboutSegment bool // ? - Should this be a theme var instead?
Settings map[string]ThemeSetting Settings map[string]ThemeSetting
Templates []TemplateMapping Templates []TemplateMapping
TemplatesMap map[string]string TemplatesMap map[string]string
@ -117,7 +116,7 @@ func (themes ThemeList) LoadActiveStatus() error {
log.Printf("Loading the default theme '%s'", theme.Name) log.Printf("Loading the default theme '%s'", theme.Name)
theme.Active = true theme.Active = true
DefaultThemeBox.Store(theme.Name) DefaultThemeBox.Store(theme.Name)
MapThemeTemplates(theme) theme.MapTemplates()
} else { } else {
log.Printf("Loading the theme '%s'", theme.Name) log.Printf("Loading the theme '%s'", theme.Name)
theme.Active = false theme.Active = false
@ -147,8 +146,8 @@ func InitThemes() error {
return err return err
} }
var theme Theme var theme = &Theme{Name: ""}
err = json.Unmarshal(themeFile, &theme) err = json.Unmarshal(themeFile, theme)
if err != nil { if err != nil {
return err return err
} }
@ -185,21 +184,25 @@ func InitThemes() error {
} }
} }
theme.ResourceTemplates = template.New("") err = theme.LoadStaticFiles()
template.Must(theme.ResourceTemplates.ParseGlob("./themes/" + theme.Name + "/public/*.css"))
// It should be safe for us to load the files for all the themes in memory, as-long as the admin hasn't setup a ridiculous number of themes
err = AddThemeStaticFiles(theme)
if err != nil { if err != nil {
return err return err
} }
Themes[theme.Name] = theme Themes[theme.Name] = theme
} }
return nil return nil
} }
func AddThemeStaticFiles(theme Theme) error { // TODO: It might be unsafe to call the template parsing functions with fsnotify, do something more concurrent
func (theme *Theme) LoadStaticFiles() error {
theme.ResourceTemplates = template.New("")
template.Must(theme.ResourceTemplates.ParseGlob("./themes/" + theme.Name + "/public/*.css"))
// It should be safe for us to load the files for all the themes in memory, as-long as the admin hasn't setup a ridiculous number of themes
return theme.AddThemeStaticFiles()
}
func (theme *Theme) AddThemeStaticFiles() error {
// TODO: Use a function instead of a closure to make this more testable? What about a function call inside the closure to take the theme variable into account? // TODO: Use a function instead of a closure to make this more testable? What about a function call inside the closure to take the theme variable into account?
return filepath.Walk("./themes/"+theme.Name+"/public", func(path string, f os.FileInfo, err error) error { return filepath.Walk("./themes/"+theme.Name+"/public", func(path string, f os.FileInfo, err error) error {
debugLog("Attempting to add static file '" + path + "' for default theme '" + theme.Name + "'") debugLog("Attempting to add static file '" + path + "' for default theme '" + theme.Name + "'")
@ -230,14 +233,14 @@ func AddThemeStaticFiles(theme Theme) error {
path = strings.TrimPrefix(path, "themes/"+theme.Name+"/public") path = strings.TrimPrefix(path, "themes/"+theme.Name+"/public")
gzipData := compressBytesGzip(data) gzipData := compressBytesGzip(data)
StaticFiles["/static/"+theme.Name+path] = SFile{data, gzipData, 0, int64(len(data)), int64(len(gzipData)), mime.TypeByExtension(ext), f, f.ModTime().UTC().Format(http.TimeFormat)} StaticFiles.Set("/static/"+theme.Name+path, SFile{data, gzipData, 0, int64(len(data)), int64(len(gzipData)), mime.TypeByExtension(ext), f, f.ModTime().UTC().Format(http.TimeFormat)})
debugLog("Added the '/" + theme.Name + path + "' static file for theme " + theme.Name + ".") debugLog("Added the '/" + theme.Name + path + "' static file for theme " + theme.Name + ".")
return nil return nil
}) })
} }
func MapThemeTemplates(theme Theme) { func (theme *Theme) MapTemplates() {
if theme.Templates != nil { if theme.Templates != nil {
for _, themeTmpl := range theme.Templates { for _, themeTmpl := range theme.Templates {
if themeTmpl.Name == "" { if themeTmpl.Name == "" {

View File

@ -22,9 +22,6 @@ go get -u gopkg.in/sourcemap.v1
echo "Installing OttoJS" echo "Installing OttoJS"
go get -u github.com/robertkrimen/otto go get -u github.com/robertkrimen/otto
echo "Installing the Riot Search Engine"
go get -u github.com/robertkrimen/otto
echo "Installing the Rez Image Resizer" echo "Installing the Rez Image Resizer"
go get -u github.com/bamiaux/rez go get -u github.com/bamiaux/rez

View File

@ -71,13 +71,6 @@ if %errorlevel% neq 0 (
exit /b %errorlevel% exit /b %errorlevel%
) )
echo Installing the Riot Search Engine
go get -u github.com/go-ego/riot
if %errorlevel% neq 0 (
pause
exit /b %errorlevel%
)
echo Installing the Rez Image Resizer echo Installing the Rez Image Resizer
go get -u github.com/bamiaux/rez go get -u github.com/bamiaux/rez
if %errorlevel% neq 0 ( if %errorlevel% neq 0 (

65
main.go
View File

@ -13,10 +13,12 @@ import (
"net/http" "net/http"
"os" "os"
"os/signal" "os/signal"
"strings"
"syscall" "syscall"
"time" "time"
//"runtime/pprof" //"runtime/pprof"
"./common" "./common"
"github.com/fsnotify/fsnotify"
) )
var version = common.Version{Major: 0, Minor: 1, Patch: 0, Tag: "dev"} var version = common.Version{Major: 0, Minor: 1, Patch: 0, Tag: "dev"}
@ -55,13 +57,11 @@ func afterDBInit() (err error) {
if err != nil { if err != nil {
return err return err
} }
log.Print("Initialising the widgets") log.Print("Initialising the widgets")
err = common.InitWidgets() err = common.InitWidgets()
if err != nil { if err != nil {
return err return err
} }
log.Print("Initialising the authentication system") log.Print("Initialising the authentication system")
common.Auth, err = common.NewDefaultAuth() common.Auth, err = common.NewDefaultAuth()
if err != nil { if err != nil {
@ -72,12 +72,10 @@ func afterDBInit() (err error) {
if err != nil { if err != nil {
return err return err
} }
common.ModLogs, err = common.NewModLogStore() common.ModLogs, err = common.NewModLogStore()
if err != nil { if err != nil {
return err return err
} }
common.AdminLogs, err = common.NewAdminLogStore() common.AdminLogs, err = common.NewAdminLogStore()
if err != nil { if err != nil {
return err return err
@ -158,6 +156,65 @@ func main() {
log.Fatal(err) log.Fatal(err)
} }
watcher, err := fsnotify.NewWatcher()
if err != nil {
log.Fatal(err)
}
defer watcher.Close()
go func() {
for {
select {
case event := <-watcher.Events:
log.Println("event:", event)
if event.Op&fsnotify.Write == fsnotify.Write {
log.Println("modified file:", event.Name)
var pathBits = strings.Split(event.Name, "\\")
if len(pathBits) > 0 {
if pathBits[0] == "themes" {
var themeName string
if len(pathBits) >= 1 {
themeName = pathBits[1]
}
if len(pathBits) >= 2 && pathBits[2] == "public" {
// TODO: Handle new themes freshly plopped into the folder?
theme, ok := common.Themes[themeName]
if ok {
err = theme.LoadStaticFiles()
if err != nil {
common.LogError(err)
}
}
}
}
}
} else if event.Op&fsnotify.Create == fsnotify.Create {
log.Println("new file:", event.Name)
}
case err := <-watcher.Errors:
log.Println("error:", err)
}
}
}()
// TODO: Keep tabs on the 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)
}
}
// Run this goroutine once a second // Run this goroutine once a second
secondTicker := time.NewTicker(1 * time.Second) secondTicker := time.NewTicker(1 * time.Second)
fifteenMinuteTicker := time.NewTicker(15 * time.Minute) fifteenMinuteTicker := time.NewTicker(15 * time.Minute)

View File

@ -42,7 +42,6 @@ func routeTopicCreate(w http.ResponseWriter, r *http.Request, user common.User,
return common.NoPermissions(w, r, user) return common.NoPermissions(w, r, user)
} }
headerVars.Zone = "create_topic" headerVars.Zone = "create_topic"
headerVars.Writer = w
// Lock this to the forum being linked? // Lock this to the forum being linked?
// Should we always put it in strictmode when it's linked from another forum? Well, the user might end up changing their mind on what forum they want to post in and it would be a hassle, if they had to switch pages, even if it is a single click for many (exc. mobile) // Should we always put it in strictmode when it's linked from another forum? Well, the user might end up changing their mind on what forum they want to post in and it would be a hassle, if they had to switch pages, even if it is a single click for many (exc. mobile)

View File

@ -1473,7 +1473,7 @@ func routePanelThemes(w http.ResponseWriter, r *http.Request, user common.User)
return common.NoPermissions(w, r, user) return common.NoPermissions(w, r, user)
} }
var pThemeList, vThemeList []common.Theme var pThemeList, vThemeList []*common.Theme
for _, theme := range common.Themes { for _, theme := range common.Themes {
if theme.HideFromThemes { if theme.HideFromThemes {
continue continue
@ -1557,7 +1557,7 @@ func routePanelThemesSetDefault(w http.ResponseWriter, r *http.Request, user com
common.DefaultThemeBox.Store(uname) common.DefaultThemeBox.Store(uname)
common.ResetTemplateOverrides() common.ResetTemplateOverrides()
common.MapThemeTemplates(theme) theme.MapTemplates()
common.ChangeDefaultThemeMutex.Unlock() common.ChangeDefaultThemeMutex.Unlock()
http.Redirect(w, r, "/panel/themes/", http.StatusSeeOther) http.Redirect(w, r, "/panel/themes/", http.StatusSeeOther)

View File

@ -42,8 +42,7 @@ func (red *HTTPSRedirect) ServeHTTP(w http.ResponseWriter, req *http.Request) {
// GET functions // GET functions
func routeStatic(w http.ResponseWriter, r *http.Request) { func routeStatic(w http.ResponseWriter, r *http.Request) {
//log.Print("Outputting static file '" + r.URL.Path + "'") file, ok := common.StaticFiles.Get(r.URL.Path)
file, ok := common.StaticFiles[r.URL.Path]
if !ok { if !ok {
if common.Dev.DebugMode { if common.Dev.DebugMode {
log.Printf("Failed to find '%s'", r.URL.Path) log.Printf("Failed to find '%s'", r.URL.Path)
@ -61,11 +60,8 @@ func routeStatic(w http.ResponseWriter, r *http.Request) {
} }
h.Set("Last-Modified", file.FormattedModTime) h.Set("Last-Modified", file.FormattedModTime)
h.Set("Content-Type", file.Mimetype) h.Set("Content-Type", file.Mimetype)
//Cache-Control: max-age=31536000 h.Set("Cache-Control", cacheControlMaxAge) //Cache-Control: max-age=31536000
h.Set("Cache-Control", cacheControlMaxAge)
h.Set("Vary", "Accept-Encoding") h.Set("Vary", "Accept-Encoding")
//http.ServeContent(w,r,r.URL.Path,file.Info.ModTime(),file)
//w.Write(file.Data)
if strings.Contains(h.Get("Accept-Encoding"), "gzip") { if strings.Contains(h.Get("Accept-Encoding"), "gzip") {
h.Set("Content-Encoding", "gzip") h.Set("Content-Encoding", "gzip")
h.Set("Content-Length", strconv.FormatInt(file.GzipLength, 10)) h.Set("Content-Length", strconv.FormatInt(file.GzipLength, 10))
@ -74,15 +70,9 @@ func routeStatic(w http.ResponseWriter, r *http.Request) {
h.Set("Content-Length", strconv.FormatInt(file.Length, 10)) // Avoid doing a type conversion every time? h.Set("Content-Length", strconv.FormatInt(file.Length, 10)) // Avoid doing a type conversion every time?
io.Copy(w, bytes.NewReader(file.Data)) io.Copy(w, bytes.NewReader(file.Data))
} }
//io.CopyN(w, bytes.NewReader(file.Data), staticFiles[r.URL.Path].Length) // Other options instead of io.Copy: io.CopyN(), w.Write(), http.ServeContent()
} }
// Deprecated: Test route for stopping the server during a performance analysis
/*func routeExit(w http.ResponseWriter, r *http.Request, user common.User) common.RouteError{
db.Close()
os.Exit(0)
}*/
// TODO: Make this a static file somehow? Is it possible for us to put this file somewhere else? // TODO: Make this a static file somehow? Is it possible for us to put this file somewhere else?
// TODO: Add a sitemap // TODO: Add a sitemap
// TODO: Add an API so that plugins can register disallowed areas. E.g. /guilds/join for plugin_guilds // TODO: Add an API so that plugins can register disallowed areas. E.g. /guilds/join for plugin_guilds
@ -102,7 +92,6 @@ func routeOverview(w http.ResponseWriter, r *http.Request, user common.User) com
return ferr return ferr
} }
headerVars.Zone = "overview" headerVars.Zone = "overview"
headerVars.Writer = w
pi := common.Page{common.GetTitlePhrase("overview"), user, headerVars, tList, nil} pi := common.Page{common.GetTitlePhrase("overview"), user, headerVars, tList, nil}
if common.PreRenderHooks["pre_render_overview"] != nil { if common.PreRenderHooks["pre_render_overview"] != nil {
@ -129,7 +118,6 @@ func routeCustomPage(w http.ResponseWriter, r *http.Request, user common.User) c
return common.NotFound(w, r) return common.NotFound(w, r)
} }
headerVars.Zone = "custom_page" headerVars.Zone = "custom_page"
headerVars.Writer = w
pi := common.Page{common.GetTitlePhrase("page"), user, headerVars, tList, nil} pi := common.Page{common.GetTitlePhrase("page"), user, headerVars, tList, nil}
if common.PreRenderHooks["pre_render_custom_page"] != nil { if common.PreRenderHooks["pre_render_custom_page"] != nil {
@ -151,7 +139,6 @@ func routeTopics(w http.ResponseWriter, r *http.Request, user common.User) commo
return ferr return ferr
} }
headerVars.Zone = "topics" headerVars.Zone = "topics"
headerVars.Writer = w
// TODO: Add a function for the qlist stuff // TODO: Add a function for the qlist stuff
var qlist string var qlist string
@ -337,7 +324,6 @@ func routeForum(w http.ResponseWriter, r *http.Request, user common.User, sfid s
return common.InternalError(err, w, r) return common.InternalError(err, w, r)
} }
headerVars.Zone = "view_forum" headerVars.Zone = "view_forum"
headerVars.Writer = w
// Calculate the offset // Calculate the offset
var offset int var offset int
@ -424,7 +410,6 @@ func routeForums(w http.ResponseWriter, r *http.Request, user common.User) commo
return ferr return ferr
} }
headerVars.Zone = "forums" headerVars.Zone = "forums"
headerVars.Writer = w
var err error var err error
var forumList []common.Forum var forumList []common.Forum
@ -514,7 +499,6 @@ func routeTopicID(w http.ResponseWriter, r *http.Request, user common.User) comm
return common.NoPermissions(w, r, user) return common.NoPermissions(w, r, user)
} }
headerVars.Zone = "view_topic" headerVars.Zone = "view_topic"
headerVars.Writer = w
topic.ContentHTML = common.ParseMessage(topic.Content, topic.ParentID, "forums") topic.ContentHTML = common.ParseMessage(topic.Content, topic.ParentID, "forums")
topic.ContentLines = strings.Count(topic.Content, "\n") topic.ContentLines = strings.Count(topic.Content, "\n")

View File

@ -495,6 +495,12 @@ select, input, textarea, button {
background-color: var(--element-background-color); background-color: var(--element-background-color);
padding: 12px; padding: 12px;
} }
/* TODO: Refactor bgavatars to make the avatars rounded */
.rowlist.bgavatars .rowitem {
background-repeat: no-repeat;
background-size: 40px;
padding-left: 46px;
}
.topic_list .rowtopic { .topic_list .rowtopic {
font-size: 17px; font-size: 17px;
@ -959,4 +965,41 @@ select, input, textarea, button {
.topic_inner_right { .topic_inner_right {
display: none; display: none;
} }
}
@media(max-width: 520px) {
.topic_list {
display: flex;
flex-wrap: wrap;
}
.topic_list .topic_row {
display: block;
width: calc(50% - 6px);
float: left;
}
.topic_list .topic_row:nth-child(odd) {
margin-right: 12px;
}
.topic_left {
margin-bottom: 0px;
border-bottom: none;
border-right: 1px solid var(--element-border-color);
}
.topic_left .parent_forum {
display: none;
}
.topic_right.rowitem {
border-top: none;
border-left: 1px solid var(--element-border-color);
background-color: hsl(0,0%,90%);
}
.topic_right br, .topic_right img {
display: none;
}
.topic_right.topic_sticky {
border-bottom: 2px solid var(--element-border-color);
}
.topic_right > span {
margin-top: 6px;
margin-bottom: 6px;
}
} }

View File

@ -6,7 +6,6 @@
"URL": "github.com/Azareal/Gosora", "URL": "github.com/Azareal/Gosora",
"Tag": "WIP", "Tag": "WIP",
"Docks":["rightSidebar","footer"], "Docks":["rightSidebar","footer"],
"AboutSegment":true,
"Templates": [ "Templates": [
{ {
"Name": "topic", "Name": "topic",

View File

@ -22,9 +22,6 @@ go get -u gopkg.in/sourcemap.v1
echo "Updating OttoJS" echo "Updating OttoJS"
go get -u github.com/robertkrimen/otto go get -u github.com/robertkrimen/otto
echo "Updating the Riot Search Engine"
go get -u github.com/go-ego/riot
echo "Updating the Rez Image Resizer" echo "Updating the Rez Image Resizer"
go get -u github.com/bamiaux/rez go get -u github.com/bamiaux/rez

View File

@ -68,13 +68,6 @@ if %errorlevel% neq 0 (
exit /b %errorlevel% exit /b %errorlevel%
) )
echo Updating the Riot Search Engine
go get -u github.com/go-ego/riot
if %errorlevel% neq 0 (
pause
exit /b %errorlevel%
)
echo Updating the Rez Image Resizer echo Updating the Rez Image Resizer
go get -u github.com/bamiaux/rez go get -u github.com/bamiaux/rez
if %errorlevel% neq 0 ( if %errorlevel% neq 0 (