From 399128c208c8dd0502e496a2774a45c5b621aa84 Mon Sep 17 00:00:00 2001
From: Azareal
Date: Sat, 11 Feb 2017 14:51:16 +0000
Subject: [PATCH] Added the topic and user memory caches. Currently used in an
extremely limited way, we plan to expand this! Added a template minifier
which strips out many types of whitespace. Stop the router from blocking the
other goroutines. Content is no longer an interface{} in the TopicUser and
Reply structs Allow plugins to add new routes without any potential race
coditions occuring Use non-breaking spaces in the topic view to make it
harder for the minifier to break things.
---
cache.go | 23 ++++
config.go | 3 +
data.sql | 1 +
install/install.go | 3 +
main.go | 14 ++-
mysql.go | 24 ++++-
plugin_helloworld.go | 3 +-
reply.go | 3 +-
router.go | 13 ++-
routes.go | 38 ++++---
template_forum.go | 2 +-
template_list.go | 22 ++--
template_profile.go | 2 +-
template_topic.go | 7 +-
template_topic_alt.go | 7 +-
template_topics.go | 2 +-
templates.go | 24 ++++-
templates/topic.html | 22 ++--
topic.go | 238 +++++++++++++++++++++++++++++++++++++++++-
user.go | 194 ++++++++++++++++++++++++++++++----
20 files changed, 561 insertions(+), 84 deletions(-)
create mode 100644 cache.go
diff --git a/cache.go b/cache.go
new file mode 100644
index 00000000..d716bd93
--- /dev/null
+++ b/cache.go
@@ -0,0 +1,23 @@
+package main
+import "errors"
+
+const CACHE_STATIC int = 0
+const CACHE_DYNAMIC int = 1
+const CACHE_SQL int = 2
+
+var ErrStoreCapacityOverflow = errors.New("This datastore has already reached it's max capacity")
+
+var users UserStore
+var topics TopicStore
+
+type DataStore interface {
+ Get(id int) (interface{}, error)
+ GetUnsafe(id int) (interface{}, error)
+ CascadeGet(id int) (interface{}, error)
+ Add(item interface{}) error
+ AddUnsafe(item interface{}) error
+ Remove(id int) error
+ RemoveUnsafe(id int) error
+ GetLength() int
+ GetCapacity() int
+}
diff --git a/config.go b/config.go
index 26cf01d6..bab4efe3 100644
--- a/config.go
+++ b/config.go
@@ -11,6 +11,9 @@ var dbport = "3306" // You probably won't need to change this
var max_request_size = 5 * megabyte
// Misc
+var cache_topicuser = CACHE_STATIC
+var user_cache_capacity = 100 // The max number of users held in memory
+var topic_cache_capacity = 100 // The max number of topics held in memory
var default_route = route_topics
var default_group = 3 // Should be a setting
var activation_group = 5 // Should be a setting
diff --git a/data.sql b/data.sql
index e2e1d6ec..08fdbc44 100644
--- a/data.sql
+++ b/data.sql
@@ -112,6 +112,7 @@ CREATE TABLE `users_replies`(
CREATE TABLE `likes`(
`weight` tinyint DEFAULT 1 not null,
+ /*`type` tinyint not null, /* Regular Post = 1, Big Post = 2, Mega Post = 3, etc.*/
`targetItem` int not null,
`targetType` varchar(50) DEFAULT 'replies' not null,
`sentBy` int not null,
diff --git a/install/install.go b/install/install.go
index 4ef0bba2..71d74ff3 100644
--- a/install/install.go
+++ b/install/install.go
@@ -149,6 +149,9 @@ var dbport = "` + db_port + `" // You probably won't need to change this
var max_request_size = 5 * megabyte
// Misc
+var cache_topicuser = CACHE_STATIC
+var user_cache_capacity = 100 // The max number of users held in memory
+var topic_cache_capacity = 100 // The max number of topics held in memory
var default_route = route_topics
var default_group = 3 // Should be a setting
var activation_group = 5 // Should be a setting
diff --git a/main.go b/main.go
index 87073ecf..87896959 100644
--- a/main.go
+++ b/main.go
@@ -51,9 +51,9 @@ func compile_templates() {
log.Print("Compiling the templates")
- topic := TopicUser{1,"Blah",template.HTML("Hey there!"),0,false,false,"Date","Date",0,"","127.0.0.1",0,1,"","",no_css_tmpl,0,"","","","",58,false}
+ topic := TopicUser{1,"Blah","Hey there!",0,false,false,"Date","Date",0,"","127.0.0.1",0,1,"",default_group,"",no_css_tmpl,0,"","","","",58,false}
var replyList []Reply
- replyList = append(replyList, Reply{0,0,"",template.HTML("Yo!"),0,"","",0,0,"",no_css_tmpl,0,"","","","",0,"127.0.0.1",false,1})
+ replyList = append(replyList, Reply{0,0,"","Yo!",0,"",default_group,"",0,0,"",no_css_tmpl,0,"","","","",0,"127.0.0.1",false,1})
var varList map[string]VarItem = make(map[string]VarItem)
tpage := TopicPage{"Title",user,noticeList,replyList,topic,1,1,false}
@@ -80,7 +80,7 @@ func compile_templates() {
topics_tmpl := c.compile_template("topics.html","templates/","TopicsPage", topics_page, varList)
var topicList []TopicUser
- topicList = append(topicList,TopicUser{1,"Topic Title","The topic content.",1,false,false,"Date","Date",1,"","127.0.0.1",0,1,"Admin","","",0,"","","","",58,false})
+ topicList = append(topicList,TopicUser{1,"Topic Title","The topic content.",1,false,false,"Date","Date",1,"","127.0.0.1",0,1,"Admin",default_group,"","",0,"","","","",58,false})
forum_item := Forum{1,"General Forum",true,"all",0,"",0,"",0,""}
forum_page := ForumPage{"General Forum",user,noticeList,topicList,forum_item,1,1,nil}
forum_tmpl := c.compile_template("forum.html","templates/","ForumPage", forum_page, varList)
@@ -143,6 +143,14 @@ func main(){
log.Fatal(err)
}
+ if cache_topicuser == CACHE_STATIC {
+ users = NewStaticUserStore(user_cache_capacity)
+ topics = NewStaticTopicStore(topic_cache_capacity)
+ } else {
+ users = NewSqlUserStore()
+ topics = NewSqlTopicStore()
+ }
+
log.Print("Loading the static files.")
err = filepath.Walk("./public", func(path string, f os.FileInfo, err error) error {
if f.IsDir() {
diff --git a/mysql.go b/mysql.go
index 6133085f..17bb2b45 100644
--- a/mysql.go
+++ b/mysql.go
@@ -9,9 +9,11 @@ import "strconv"
import "encoding/json"
var db *sql.DB
-var get_session_stmt *sql.Stmt
+var get_user_stmt *sql.Stmt
+var get_full_user_stmt *sql.Stmt
var get_topic_list_stmt *sql.Stmt
var get_topic_user_stmt *sql.Stmt
+var get_topic_stmt *sql.Stmt
var get_topic_replies_stmt *sql.Stmt
var get_topic_replies_offset_stmt *sql.Stmt
var get_forum_topics_stmt *sql.Stmt
@@ -90,8 +92,20 @@ func init_database(err error) {
log.Fatal(err)
}
- log.Print("Preparing get_session statement.")
+ /*log.Print("Preparing get_session statement.")
get_session_stmt, err = db.Prepare("select `uid`,`name`,`group`,`is_super_admin`,`session`,`email`,`avatar`,`message`,`url_prefix`,`url_name`,`level`,`score`,`last_ip` from `users` where `uid` = ? and `session` = ? AND `session` <> ''")
+ if err != nil {
+ log.Fatal(err)
+ }*/
+
+ log.Print("Preparing get_user statement.")
+ get_user_stmt, err = db.Prepare("select `name`,`group`,`is_super_admin`,`avatar`,`message`,`url_prefix`,`url_name`,`level` from `users` where `uid` = ?")
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ log.Print("Preparing get_full_user statement.")
+ get_full_user_stmt, err = db.Prepare("select `name`,`group`,`is_super_admin`,`session`,`email`,`avatar`,`message`,`url_prefix`,`url_name`,`level`,`score`,`last_ip` from `users` where `uid` = ?")
if err != nil {
log.Fatal(err)
}
@@ -108,6 +122,12 @@ func init_database(err error) {
log.Fatal(err)
}
+ log.Print("Preparing get_topic statement.")
+ get_topic_stmt, err = db.Prepare("select title, content, createdBy, createdAt, is_closed, sticky, parentID, ipaddress, postCount, likeCount from topics where tid = ?")
+ if err != nil {
+ log.Fatal(err)
+ }
+
log.Print("Preparing get_topic_replies statement.")
get_topic_replies_stmt, err = db.Prepare("select replies.rid, replies.content, replies.createdBy, replies.createdAt, replies.lastEdit, replies.lastEditBy, users.avatar, users.name, users.group, users.url_prefix, users.url_name, users.level, replies.ipaddress from replies left join users ON replies.createdBy = users.uid where tid = ?")
if err != nil {
diff --git a/plugin_helloworld.go b/plugin_helloworld.go
index df742435..0eef0c47 100644
--- a/plugin_helloworld.go
+++ b/plugin_helloworld.go
@@ -1,5 +1,4 @@
package main
-import "html/template"
func init() {
plugins["helloworld"] = NewPlugin("helloworld","Hello World","Azareal","http://github.com/Azareal","","","",init_helloworld,nil,deactivate_helloworld)
@@ -17,7 +16,7 @@ func deactivate_helloworld() {
func helloworld_reply(data interface{}) interface{} {
reply := data.(Reply)
reply.Content = "Hello World!"
- reply.ContentHtml = template.HTML("Hello World!")
+ reply.ContentHtml = "Hello World!"
reply.Tag = "Auto"
return reply
}
\ No newline at end of file
diff --git a/reply.go b/reply.go
index 98a4a183..b0cfb088 100644
--- a/reply.go
+++ b/reply.go
@@ -7,9 +7,10 @@ type Reply struct
ID int
ParentID int
Content string
- ContentHtml template.HTML
+ ContentHtml string
CreatedBy int
CreatedByName string
+ Group int
CreatedAt string
LastEdit int
LastEditBy int
diff --git a/router.go b/router.go
index 6253c7d0..b48f3472 100644
--- a/router.go
+++ b/router.go
@@ -16,38 +16,45 @@ func NewRouter() *Router {
}
func (router *Router) Handle(pattern string, handle http.Handler) {
+ router.mu.Lock()
router.routes[pattern] = handle.ServeHTTP
+ router.mu.Unlock()
}
func (router *Router) HandleFunc(pattern string, handle func(http.ResponseWriter, *http.Request)) {
+ router.mu.Lock()
router.routes[pattern] = handle
+ router.mu.Unlock()
}
func (router *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
router.mu.RLock()
- defer router.mu.RUnlock()
if req.URL.Path[0] != '/' {
+ router.mu.RUnlock()
w.WriteHeader(405)
w.Write([]byte(""))
return
}
- // Do something on the path to turn slashes facing the wrong way "\" into "/" slashes. If it's bytes, then alter the bytes in place for the maximum speed
+ // Do something on the path to turn slashes facing the wrong way "\" into "/" slashes. Like what? Wouldn't that be slow? Might need to move to fasthttp for this
handle, ok := router.routes[req.URL.Path]
if ok {
+ router.mu.RUnlock()
handle(w,req)
return
}
if req.URL.Path[len(req.URL.Path) - 1] == '/' {
+ router.mu.RUnlock()
NotFound(w,req)
return
}
handle, ok = router.routes[req.URL.Path[:strings.LastIndexByte(req.URL.Path,'/') + 1]]
if ok {
+ router.mu.RUnlock()
handle(w,req)
return
}
@@ -55,10 +62,12 @@ func (router *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
handle, ok = router.routes[req.URL.Path + "/"]
if ok {
+ router.mu.RUnlock()
handle(w,req)
return
}
+ router.mu.RUnlock()
NotFound(w,req)
return
}
\ No newline at end of file
diff --git a/routes.go b/routes.go
index 8403d28d..342ee083 100644
--- a/routes.go
+++ b/routes.go
@@ -288,23 +288,20 @@ func route_forums(w http.ResponseWriter, r *http.Request){
func route_topic_id(w http.ResponseWriter, r *http.Request){
var(
err error
- content string
- group int
page int
offset int
replyList []Reply
)
page, _ = strconv.Atoi(r.FormValue("page"))
- topic := TopicUser{Css: no_css_tmpl}
- topic.ID, err = strconv.Atoi(r.URL.Path[len("/topic/"):])
+ tid, err := strconv.Atoi(r.URL.Path[len("/topic/"):])
if err != nil {
PreError("The provided TopicID is not a valid number.",w,r)
return
}
// Get the topic..
- err = get_topic_user_stmt.QueryRow(topic.ID).Scan(&topic.Title, &content, &topic.CreatedBy, &topic.CreatedAt, &topic.Is_Closed, &topic.Sticky, &topic.ParentID, &topic.IpAddress, &topic.PostCount, &topic.LikeCount, &topic.CreatedByName, &topic.Avatar, &group, &topic.URLPrefix, &topic.URLName, &topic.Level)
+ topic, err := get_topicuser(tid)
if err == sql.ErrNoRows {
NotFound(w,r)
return
@@ -312,6 +309,7 @@ func route_topic_id(w http.ResponseWriter, r *http.Request){
InternalError(err,w,r)
return
}
+ topic.Css = no_css_tmpl
user, noticeList, ok := ForumSessionCheck(w,r,topic.ParentID)
if !ok {
@@ -322,8 +320,8 @@ func route_topic_id(w http.ResponseWriter, r *http.Request){
return
}
- topic.Content = template.HTML(parse_message(content))
- topic.ContentLines = strings.Count(content,"\n")
+ topic.ContentLines = strings.Count(topic.Content,"\n")
+ topic.Content = parse_message(topic.Content)
// We don't want users posting in locked topics...
if topic.Is_Closed && !user.Is_Mod {
@@ -337,12 +335,12 @@ func route_topic_id(w http.ResponseWriter, r *http.Request){
} else {
topic.Avatar = strings.Replace(noavatar,"{id}",strconv.Itoa(topic.CreatedBy),1)
}
- if groups[group].Is_Mod || groups[group].Is_Admin {
+ if groups[topic.Group].Is_Mod || groups[topic.Group].Is_Admin {
topic.Css = staff_css_tmpl
topic.Level = -1
}
- topic.Tag = groups[group].Tag
+ topic.Tag = groups[topic.Group].Tag
if settings["url_tags"] == false {
topic.URLName = ""
@@ -382,16 +380,16 @@ func route_topic_id(w http.ResponseWriter, r *http.Request){
replyItem := Reply{Css: no_css_tmpl}
for rows.Next() {
- err := rows.Scan(&replyItem.ID, &replyItem.Content, &replyItem.CreatedBy, &replyItem.CreatedAt, &replyItem.LastEdit, &replyItem.LastEditBy, &replyItem.Avatar, &replyItem.CreatedByName, &group, &replyItem.URLPrefix, &replyItem.URLName, &replyItem.Level, &replyItem.IpAddress, &replyItem.LikeCount)
+ err := rows.Scan(&replyItem.ID, &replyItem.Content, &replyItem.CreatedBy, &replyItem.CreatedAt, &replyItem.LastEdit, &replyItem.LastEditBy, &replyItem.Avatar, &replyItem.CreatedByName, &replyItem.Group, &replyItem.URLPrefix, &replyItem.URLName, &replyItem.Level, &replyItem.IpAddress, &replyItem.LikeCount)
if err != nil {
InternalError(err,w,r)
return
}
replyItem.ParentID = topic.ID
- replyItem.ContentHtml = template.HTML(parse_message(replyItem.Content))
+ replyItem.ContentHtml = parse_message(replyItem.Content)
replyItem.ContentLines = strings.Count(replyItem.Content,"\n")
- if groups[group].Is_Mod || groups[group].Is_Admin {
+ if groups[replyItem.Group].Is_Mod || groups[replyItem.Group].Is_Admin {
replyItem.Css = staff_css_tmpl
replyItem.Level = -1
} else {
@@ -405,7 +403,7 @@ func route_topic_id(w http.ResponseWriter, r *http.Request){
replyItem.Avatar = strings.Replace(noavatar,"{id}",strconv.Itoa(replyItem.CreatedBy),1)
}
- replyItem.Tag = groups[group].Tag
+ replyItem.Tag = groups[replyItem.Group].Tag
/*if settings["url_tags"] == false {
replyItem.URLName = ""
@@ -462,7 +460,7 @@ func route_profile(w http.ResponseWriter, r *http.Request){
replyCss template.CSS
replyLines int
replyTag string
- group int
+ replyGroup int
replyList []Reply
)
@@ -479,7 +477,7 @@ func route_profile(w http.ResponseWriter, r *http.Request){
puser = user
} else {
// Fetch the user data
- err = db.QueryRow("select `name`,`group`,`is_super_admin`,`avatar`,`message`,`url_prefix`,`url_name`,`level` from `users` where `uid` = ?", puser.ID).Scan(&puser.Name, &puser.Group, &puser.Is_Super_Admin, &puser.Avatar, &puser.Message, &puser.URLPrefix, &puser.URLName, &puser.Level)
+ err = get_user_stmt.QueryRow(puser.ID).Scan(&puser.Name, &puser.Group, &puser.Is_Super_Admin, &puser.Avatar, &puser.Message, &puser.URLPrefix, &puser.URLName, &puser.Level)
if err == sql.ErrNoRows {
NotFound(w,r)
return
@@ -516,14 +514,14 @@ func route_profile(w http.ResponseWriter, r *http.Request){
defer rows.Close()
for rows.Next() {
- err := rows.Scan(&rid, &replyContent, &replyCreatedBy, &replyCreatedAt, &replyLastEdit, &replyLastEditBy, &replyAvatar, &replyCreatedByName, &group)
+ err := rows.Scan(&rid, &replyContent, &replyCreatedBy, &replyCreatedAt, &replyLastEdit, &replyLastEditBy, &replyAvatar, &replyCreatedByName, &replyGroup)
if err != nil {
InternalError(err,w,r)
return
}
replyLines = strings.Count(replyContent,"\n")
- if groups[group].Is_Mod || groups[group].Is_Admin {
+ if groups[replyGroup].Is_Mod || groups[replyGroup].Is_Admin {
replyCss = staff_css_tmpl
} else {
replyCss = no_css_tmpl
@@ -536,8 +534,8 @@ func route_profile(w http.ResponseWriter, r *http.Request){
replyAvatar = strings.Replace(noavatar,"{id}",strconv.Itoa(replyCreatedBy),1)
}
- if groups[group].Tag != "" {
- replyTag = groups[group].Tag
+ if groups[replyGroup].Tag != "" {
+ replyTag = groups[replyGroup].Tag
} else if puser.ID == replyCreatedBy {
replyTag = "Profile Owner"
} else {
@@ -547,7 +545,7 @@ func route_profile(w http.ResponseWriter, r *http.Request){
replyLiked := false
replyLikeCount := 0
- replyList = append(replyList, Reply{rid,puser.ID,replyContent,template.HTML(parse_message(replyContent)),replyCreatedBy,replyCreatedByName,replyCreatedAt,replyLastEdit,replyLastEditBy,replyAvatar,replyCss,replyLines,replyTag,"","","",0,"",replyLiked,replyLikeCount})
+ replyList = append(replyList, Reply{rid,puser.ID,replyContent,parse_message(replyContent),replyCreatedBy,replyCreatedByName,replyGroup,replyCreatedAt,replyLastEdit,replyLastEditBy,replyAvatar,replyCss,replyLines,replyTag,"","","",0,"",replyLiked,replyLikeCount})
}
err = rows.Err()
if err != nil {
diff --git a/template_forum.go b/template_forum.go
index a9f00d89..55a5e609 100644
--- a/template_forum.go
+++ b/template_forum.go
@@ -1,7 +1,7 @@
/* This file was automatically generated by the software. Please don't edit it as your changes may be overwritten at any moment. */
package main
-import "strconv"
import "io"
+import "strconv"
func init() {
template_forum_handle = template_forum
diff --git a/template_list.go b/template_list.go
index b917b1f0..535f3350 100644
--- a/template_list.go
+++ b/template_list.go
@@ -97,25 +97,25 @@ var topic_23 []byte = []byte(`
var topic_24 []byte = []byte(`
`)
-var topic_26 []byte = []byte(`
+var topic_26 []byte = []byte(`
+var topic_29 []byte = []byte(`">😀
`)
var topic_30 []byte = []byte(`
+var topic_31 []byte = []byte(`' class="mod_button open_edit" style="font-weight:normal;" title="Edit Topic">
+var topic_32 []byte = []byte(`' class="mod_button" style="font-weight:normal;" title="Delete Topic">
`)
var topic_33 []byte = []byte(``)
var topic_35 []byte = []byte(``)
-var topic_37 []byte = []byte(`
+var topic_37 []byte = []byte(`
+var topic_39 []byte = []byte(`&type=topic" class="mod_button report_item" style="font-weight:normal;" title="Flag Topic">
`)
var topic_40 []byte = []byte(``)
var topic_41 []byte = []byte(`😀`)
@@ -138,20 +138,20 @@ var topic_52 []byte = []byte(`">
var topic_53 []byte = []byte(`
`)
-var topic_55 []byte = []byte(`
+var topic_55 []byte = []byte(`
+var topic_58 []byte = []byte(`">😀
`)
var topic_59 []byte = []byte(` `)
+var topic_60 []byte = []byte(`" class="mod_button" title="Edit Reply"> `)
var topic_61 []byte = []byte(` `)
+var topic_62 []byte = []byte(`" class="mod_button" title="Delete Reply"> `)
var topic_63 []byte = []byte(`
+var topic_65 []byte = []byte(`&type=reply" class="mod_button" title="Flag Reply">
`)
var topic_66 []byte = []byte(``)
var topic_67 []byte = []byte(`😀`)
diff --git a/template_profile.go b/template_profile.go
index 609431b3..71e1c103 100644
--- a/template_profile.go
+++ b/template_profile.go
@@ -85,7 +85,7 @@ w.Write(profile_19)
w.Write([]byte(string(item.Css)))
}
w.Write(profile_20)
-w.Write([]byte(string(item.ContentHtml)))
+w.Write([]byte(item.ContentHtml))
w.Write(profile_21)
w.Write([]byte(strconv.Itoa(item.CreatedBy)))
w.Write(profile_22)
diff --git a/template_topic.go b/template_topic.go
index 16824192..81b8b7a6 100644
--- a/template_topic.go
+++ b/template_topic.go
@@ -1,7 +1,6 @@
/* This file was automatically generated by the software. Please don't edit it as your changes may be overwritten at any moment. */
package main
import "strconv"
-import "html/template"
import "io"
func init() {
@@ -92,9 +91,9 @@ w.Write(topic_21)
w.Write([]byte(string(tmpl_topic_vars.Topic.Css)))
}
w.Write(topic_22)
-w.Write([]byte(string(tmpl_topic_vars.Topic.Content.(template.HTML))))
+w.Write([]byte(tmpl_topic_vars.Topic.Content))
w.Write(topic_23)
-w.Write([]byte(string(tmpl_topic_vars.Topic.Content.(template.HTML))))
+w.Write([]byte(tmpl_topic_vars.Topic.Content))
w.Write(topic_24)
w.Write([]byte(strconv.Itoa(tmpl_topic_vars.Topic.CreatedBy)))
w.Write(topic_25)
@@ -156,7 +155,7 @@ w.Write(topic_51)
w.Write([]byte(string(item.Css)))
}
w.Write(topic_52)
-w.Write([]byte(string(item.ContentHtml)))
+w.Write([]byte(item.ContentHtml))
w.Write(topic_53)
w.Write([]byte(strconv.Itoa(item.CreatedBy)))
w.Write(topic_54)
diff --git a/template_topic_alt.go b/template_topic_alt.go
index fbf6737f..913eebc4 100644
--- a/template_topic_alt.go
+++ b/template_topic_alt.go
@@ -2,7 +2,6 @@
package main
import "io"
import "strconv"
-import "html/template"
func init() {
template_topic_alt_handle = template_topic_alt
@@ -117,9 +116,9 @@ w.Write([]byte(strconv.Itoa(tmpl_topic_alt_vars.Topic.Level)))
w.Write(topic_alt_34)
}
w.Write(topic_alt_35)
-w.Write([]byte(string(tmpl_topic_alt_vars.Topic.Content.(template.HTML))))
+w.Write([]byte(tmpl_topic_alt_vars.Topic.Content))
w.Write(topic_alt_36)
-w.Write([]byte(string(tmpl_topic_alt_vars.Topic.Content.(template.HTML))))
+w.Write([]byte(tmpl_topic_alt_vars.Topic.Content))
w.Write(topic_alt_37)
if tmpl_topic_alt_vars.CurrentUser.Perms.ViewIPs {
w.Write(topic_alt_38)
@@ -146,7 +145,7 @@ w.Write([]byte(strconv.Itoa(item.Level)))
w.Write(topic_alt_48)
}
w.Write(topic_alt_49)
-w.Write([]byte(string(item.ContentHtml)))
+w.Write([]byte(item.ContentHtml))
w.Write(topic_alt_50)
if tmpl_topic_alt_vars.CurrentUser.Perms.EditReply {
w.Write(topic_alt_51)
diff --git a/template_topics.go b/template_topics.go
index e42c7c91..25153d41 100644
--- a/template_topics.go
+++ b/template_topics.go
@@ -1,7 +1,7 @@
/* This file was automatically generated by the software. Please don't edit it as your changes may be overwritten at any moment. */
package main
-import "io"
import "strconv"
+import "io"
func init() {
template_topics_handle = template_topics
diff --git a/templates.go b/templates.go
index aba5797a..a5d68774 100644
--- a/templates.go
+++ b/templates.go
@@ -12,6 +12,11 @@ import "text/template/parse"
var ctemplates []string
var tmpl_ptr_map map[string]interface{} = make(map[string]interface{})
+var text_overlap_list map[string]int
+
+func init() {
+ text_overlap_list = make(map[string]int)
+}
type VarItem struct
{
@@ -80,7 +85,11 @@ func (c *CTemplateSet) compile_template(name string, dir string, expects string,
if err != nil {
log.Fatal(err)
}
+
content := string(res)
+ if !debug {
+ content = minify(content)
+ }
tree := parse.New(name, c.funcMap)
var treeSet map[string]*parse.Tree = make(map[string]*parse.Tree)
@@ -822,7 +831,11 @@ func (c *CTemplateSet) compile_subtemplate(pvarholder string, pholdreflect refle
if err != nil {
log.Fatal(err)
}
+
content := string(res)
+ if !debug {
+ content = minify(content)
+ }
tree := parse.New(node.Name, c.funcMap)
var treeSet map[string]*parse.Tree = make(map[string]*parse.Tree)
@@ -859,4 +872,13 @@ func (c *CTemplateSet) compile_subtemplate(pvarholder string, pholdreflect refle
func (c *CTemplateSet) compile_command(*parse.CommandNode) (out string) {
return ""
-}
\ No newline at end of file
+}
+
+func minify(data string) string {
+ data = strings.Replace(data,"\t","",-1)
+ data = strings.Replace(data,"\v","",-1)
+ data = strings.Replace(data,"\n","",-1)
+ data = strings.Replace(data,"\r","",-1)
+ data = strings.Replace(data," "," ",-1)
+ return data
+}
diff --git a/templates/topic.html b/templates/topic.html
index 506e9134..bc0b373d 100644
--- a/templates/topic.html
+++ b/templates/topic.html
@@ -22,13 +22,13 @@
{{.Topic.Content}}
-
{{.Topic.CreatedByName}}
-
- {{if .CurrentUser.Is_Mod}}
-
+
{{.Topic.CreatedByName}}
+
+ {{if .CurrentUser.Is_Mod}}
+
{{if .Topic.Sticky}}
{{else}}
{{end}}
- {{end}}
-
+ {{end}}
+
{{if .Topic.LikeCount}}
{{.Topic.LikeCount}}😀{{end}}
{{if .Topic.Tag}}
{{.Topic.Tag}}{{else}}
{{.Topic.Level}}👑{{end}}
@@ -36,11 +36,11 @@
{{range .ItemList}}
{{.ContentHtml}}
-
{{.CreatedByName}}
-
- {{if $.CurrentUser.Perms.EditReply}}
{{end}}
- {{if $.CurrentUser.Perms.DeleteReply}}
{{end}}
-
+
{{.CreatedByName}}
+
+ {{if $.CurrentUser.Perms.EditReply}}
{{end}}
+ {{if $.CurrentUser.Perms.DeleteReply}}
{{end}}
+
{{if .LikeCount}}
{{.LikeCount}}😀{{end}}
{{if .Tag}}
{{.Tag}}{{else}}
{{.Level}}👑{{end}}
diff --git a/topic.go b/topic.go
index 8890b419..162263af 100644
--- a/topic.go
+++ b/topic.go
@@ -1,5 +1,8 @@
package main
+//import "fmt"
+import "sync"
import "html/template"
+import "database/sql"
type Topic struct
{
@@ -10,17 +13,19 @@ type Topic struct
Is_Closed bool
Sticky bool
CreatedAt string
+ LastReplyAt string
ParentID int
Status string // Deprecated. Marked for removal.
IpAddress string
PostCount int
+ LikeCount int
}
type TopicUser struct
{
ID int
Title string
- Content interface{}
+ Content string
CreatedBy int
Is_Closed bool
Sticky bool
@@ -33,6 +38,7 @@ type TopicUser struct
LikeCount int
CreatedByName string
+ Group int
Avatar string
Css template.CSS
ContentLines int
@@ -48,7 +54,7 @@ type TopicsRow struct
{
ID int
Title string
- Content interface{}
+ Content string
CreatedBy int
Is_Closed bool
Sticky bool
@@ -72,3 +78,231 @@ type TopicsRow struct
ForumName string //TopicsRow
}
+
+type TopicStore interface {
+ Get(id int) (*Topic, error)
+ GetUnsafe(id int) (*Topic, error)
+ CascadeGet(id int) (*Topic, error)
+ Add(item *Topic) error
+ AddUnsafe(item *Topic) error
+ Remove(id int) error
+ RemoveUnsafe(id int) error
+ AddLastTopic(item *Topic, fid int) error
+ GetLength() int
+ GetCapacity() int
+}
+
+type StaticTopicStore struct {
+ items map[int]*Topic
+ length int
+ capacity int
+ mu sync.RWMutex
+}
+
+func NewStaticTopicStore(capacity int) *StaticTopicStore {
+ return &StaticTopicStore{items:make(map[int]*Topic),capacity:capacity}
+}
+
+func (sts *StaticTopicStore) Get(id int) (*Topic, error) {
+ sts.mu.RLock()
+ item, ok := sts.items[id]
+ sts.mu.RUnlock()
+ if ok {
+ return item, nil
+ }
+ return item, sql.ErrNoRows
+}
+
+func (sts *StaticTopicStore) GetUnsafe(id int) (*Topic, error) {
+ item, ok := sts.items[id]
+ if ok {
+ return item, nil
+ }
+ return item, sql.ErrNoRows
+}
+
+func (sts *StaticTopicStore) CascadeGet(id int) (*Topic, error) {
+ sts.mu.RLock()
+ topic, ok := sts.items[id]
+ sts.mu.RUnlock()
+ if ok {
+ return topic, nil
+ }
+
+ topic = &Topic{ID:id}
+ err := get_topic_stmt.QueryRow(id).Scan(&topic.Title, &topic.Content, &topic.CreatedBy, &topic.CreatedAt, &topic.Is_Closed, &topic.Sticky, &topic.ParentID, &topic.IpAddress, &topic.PostCount, &topic.LikeCount)
+ if err == nil {
+ sts.Add(topic)
+ }
+ return topic, err
+}
+
+func (sts *StaticTopicStore) Add(item *Topic) error {
+ if sts.length >= sts.capacity {
+ return ErrStoreCapacityOverflow
+ }
+ sts.mu.Lock()
+ sts.items[item.ID] = item
+ sts.mu.Unlock()
+ sts.length++
+ return nil
+}
+
+func (sts *StaticTopicStore) AddUnsafe(item *Topic) error {
+ if sts.length >= sts.capacity {
+ return ErrStoreCapacityOverflow
+ }
+ sts.items[item.ID] = item
+ sts.length++
+ return nil
+}
+
+func (sts *StaticTopicStore) Remove(id int) error {
+ sts.mu.Lock()
+ delete(sts.items,id)
+ sts.mu.Unlock()
+ sts.length--
+ return nil
+}
+
+func (sts *StaticTopicStore) RemoveUnsafe(id int) error {
+ delete(sts.items,id)
+ sts.length--
+ return nil
+}
+
+func (sts *StaticTopicStore) AddLastTopic(item *Topic, fid int) error {
+ // Coming Soon...
+ return nil
+}
+
+func (sts *StaticTopicStore) GetLength() int {
+ return sts.length
+}
+
+func (sts *StaticTopicStore) SetCapacity(capacity int) {
+ sts.capacity = capacity
+}
+
+func (sts *StaticTopicStore) GetCapacity() int {
+ return sts.capacity
+}
+
+//type DynamicTopicStore struct {
+// items_expiries list.List
+// items map[int]*Topic
+//}
+
+type SqlTopicStore struct {
+}
+
+func NewSqlTopicStore() *SqlTopicStore {
+ return &SqlTopicStore{}
+}
+
+func (sus *SqlTopicStore) Get(id int) (*Topic, error) {
+ topic := Topic{ID:id}
+ err := get_topic_stmt.QueryRow(id).Scan(&topic.Title, &topic.Content, &topic.CreatedBy, &topic.CreatedAt, &topic.Is_Closed, &topic.Sticky, &topic.ParentID, &topic.IpAddress, &topic.PostCount, &topic.LikeCount)
+ return &topic, err
+}
+
+func (sus *SqlTopicStore) GetUnsafe(id int) (*Topic, error) {
+ topic := Topic{ID:id}
+ err := get_topic_stmt.QueryRow(id).Scan(&topic.Title, &topic.Content, &topic.CreatedBy, &topic.CreatedAt, &topic.Is_Closed, &topic.Sticky, &topic.ParentID, &topic.IpAddress, &topic.PostCount, &topic.LikeCount)
+ return &topic, err
+}
+
+func (sus *SqlTopicStore) CascadeGet(id int) (*Topic, error) {
+ topic := Topic{ID:id}
+ err := get_topic_stmt.QueryRow(id).Scan(&topic.Title, &topic.Content, &topic.CreatedBy, &topic.CreatedAt, &topic.Is_Closed, &topic.Sticky, &topic.ParentID, &topic.IpAddress, &topic.PostCount, &topic.LikeCount)
+ return &topic, err
+}
+
+// Placeholder methods, the actual queries are done elsewhere
+func (sus *SqlTopicStore) Add(item *Topic) error {
+ return nil
+}
+func (sus *SqlTopicStore) AddUnsafe(item *Topic) error {
+ return nil
+}
+func (sus *SqlTopicStore) Remove(id int) error {
+ return nil
+}
+func (sus *SqlTopicStore) RemoveUnsafe(id int) error {
+ return nil
+}
+func (sts *SqlTopicStore) AddLastTopic(item *Topic, fid int) error {
+ // Coming Soon...
+ return nil
+}
+func (sts *SqlTopicStore) GetCapacity() int {
+ return 0
+}
+
+func (sus *SqlTopicStore) GetLength() int {
+ // Return the total number of topics on the forums
+ return 0
+}
+
+func get_topicuser(tid int) (TopicUser,error) {
+ if cache_topicuser != CACHE_SQL {
+ topic, err := topics.Get(tid)
+ if err == nil {
+ user, err := users.CascadeGet(topic.CreatedBy)
+ if err != nil {
+ return TopicUser{ID:tid}, err
+ }
+
+ // We might be better off just passing seperate topic and user structs to the caller?
+ return copy_topic_to_topicuser(topic, user), nil
+ } else if users.GetLength() < users.GetCapacity() {
+ topic, err = topics.CascadeGet(tid)
+ if err != nil {
+ return TopicUser{ID:tid}, err
+ }
+ user, err := users.CascadeGet(topic.CreatedBy)
+ if err != nil {
+ return TopicUser{ID:tid}, err
+ }
+ tu := copy_topic_to_topicuser(topic, user)
+ //fmt.Printf("%+v\n", topic)
+ //fmt.Println("")
+ //fmt.Printf("%+v\n", user)
+ //fmt.Println("")
+ //fmt.Printf("%+v\n", tu)
+ //fmt.Println("")
+ return tu, nil
+ }
+ }
+
+ tu := TopicUser{ID:tid}
+ err := get_topic_user_stmt.QueryRow(tid).Scan(&tu.Title, &tu.Content, &tu.CreatedBy, &tu.CreatedAt, &tu.Is_Closed, &tu.Sticky, &tu.ParentID, &tu.IpAddress, &tu.PostCount, &tu.LikeCount, &tu.CreatedByName, &tu.Avatar, &tu.Group, &tu.URLPrefix, &tu.URLName, &tu.Level)
+
+ the_topic := Topic{ID:tu.ID, Title:tu.Title, Content:tu.Content, CreatedBy:tu.CreatedBy, Is_Closed:tu.Is_Closed, Sticky:tu.Sticky, CreatedAt:tu.CreatedAt, LastReplyAt:tu.LastReplyAt, ParentID:tu.ParentID, IpAddress:tu.IpAddress, PostCount:tu.PostCount, LikeCount:tu.LikeCount}
+ //fmt.Printf("%+v\n", the_topic)
+ topics.Add(&the_topic)
+ return tu, err
+}
+
+func copy_topic_to_topicuser(topic *Topic, user *User) (tu TopicUser) {
+ tu.CreatedByName = user.Name
+ tu.Group = user.Group
+ tu.Avatar = user.Avatar
+ tu.URLPrefix = user.URLPrefix
+ tu.URLName = user.URLName
+ tu.Level = user.Level
+
+ tu.ID = topic.ID
+ tu.Title = topic.Title
+ tu.Content = topic.Content
+ tu.CreatedBy = topic.CreatedBy
+ tu.Is_Closed = topic.Is_Closed
+ tu.Sticky = topic.Sticky
+ tu.CreatedAt = topic.CreatedAt
+ tu.LastReplyAt = topic.LastReplyAt
+ tu.ParentID = topic.ParentID
+ tu.IpAddress = topic.IpAddress
+ tu.PostCount = topic.PostCount
+ tu.LikeCount = topic.LikeCount
+ return tu
+}
\ No newline at end of file
diff --git a/user.go b/user.go
index adfc1285..602fd3f8 100644
--- a/user.go
+++ b/user.go
@@ -1,5 +1,6 @@
package main
//import "fmt"
+import "sync"
import "strings"
import "strconv"
import "net"
@@ -42,6 +43,161 @@ type Email struct
Token string
}
+type UserStore interface {
+ Get(id int) (*User, error)
+ GetUnsafe(id int) (*User, error)
+ CascadeGet(id int) (*User, error)
+ Add(item *User) error
+ AddUnsafe(item *User) error
+ Remove(id int) error
+ RemoveUnsafe(id int) error
+ GetLength() int
+ GetCapacity() int
+}
+
+type StaticUserStore struct {
+ items map[int]*User
+ length int
+ capacity int
+ mu sync.RWMutex
+}
+
+func NewStaticUserStore(capacity int) *StaticUserStore {
+ return &StaticUserStore{items:make(map[int]*User),capacity:capacity}
+}
+
+func (sts *StaticUserStore) Get(id int) (*User, error) {
+ sts.mu.RLock()
+ item, ok := sts.items[id]
+ sts.mu.RUnlock()
+ if ok {
+ return item, nil
+ }
+ return item, sql.ErrNoRows
+}
+
+func (sts *StaticUserStore) GetUnsafe(id int) (*User, error) {
+ item, ok := sts.items[id]
+ if ok {
+ return item, nil
+ }
+ return item, sql.ErrNoRows
+}
+
+func (sts *StaticUserStore) CascadeGet(id int) (*User, error) {
+ sts.mu.RLock()
+ user, ok := sts.items[id]
+ sts.mu.RUnlock()
+ if ok {
+ return user, nil
+ }
+
+ user = &User{ID:id}
+ err := get_full_user_stmt.QueryRow(id).Scan(&user.Name, &user.Group, &user.Is_Super_Admin, &user.Session, &user.Email, &user.Avatar, &user.Message, &user.URLPrefix, &user.URLName, &user.Level, &user.Score, &user.Last_IP)
+ if err == nil {
+ sts.Add(user)
+ }
+ return user, err
+}
+
+func (sts *StaticUserStore) Add(item *User) error {
+ if sts.length >= sts.capacity {
+ return ErrStoreCapacityOverflow
+ }
+ sts.mu.Lock()
+ sts.items[item.ID] = item
+ sts.mu.Unlock()
+ sts.length++
+ return nil
+}
+
+func (sts *StaticUserStore) AddUnsafe(item *User) error {
+ if sts.length >= sts.capacity {
+ return ErrStoreCapacityOverflow
+ }
+ sts.items[item.ID] = item
+ sts.length++
+ return nil
+}
+
+func (sts *StaticUserStore) Remove(id int) error {
+ sts.mu.Lock()
+ delete(sts.items,id)
+ sts.mu.Unlock()
+ sts.length--
+ return nil
+}
+
+func (sts *StaticUserStore) RemoveUnsafe(id int) error {
+ delete(sts.items,id)
+ sts.length--
+ return nil
+}
+
+func (sts *StaticUserStore) GetLength() int {
+ return sts.length
+}
+
+func (sts *StaticUserStore) SetCapacity(capacity int) {
+ sts.capacity = capacity
+}
+
+func (sts *StaticUserStore) GetCapacity() int {
+ return sts.capacity
+}
+
+//type DynamicUserStore struct {
+// items_expiries list.List
+// items map[int]*User
+//}
+
+type SqlUserStore struct {
+}
+
+func NewSqlUserStore() *SqlUserStore {
+ return &SqlUserStore{}
+}
+
+func (sus *SqlUserStore) Get(id int) (*User, error) {
+ user := User{ID:id}
+ err := get_full_user_stmt.QueryRow(id).Scan(&user.Name, &user.Group, &user.Is_Super_Admin, &user.Session, &user.Email, &user.Avatar, &user.Message, &user.URLPrefix, &user.URLName, &user.Level, &user.Score, &user.Last_IP)
+ return &user, err
+}
+
+func (sus *SqlUserStore) GetUnsafe(id int) (*User, error) {
+ user := User{ID:id}
+ err := get_full_user_stmt.QueryRow(id).Scan(&user.Name, &user.Group, &user.Is_Super_Admin, &user.Session, &user.Email, &user.Avatar, &user.Message, &user.URLPrefix, &user.URLName, &user.Level, &user.Score, &user.Last_IP)
+ return &user, err
+}
+
+func (sus *SqlUserStore) CascadeGet(id int) (*User, error) {
+ user := User{ID:id}
+ err := get_full_user_stmt.QueryRow(id).Scan(&user.Name, &user.Group, &user.Is_Super_Admin, &user.Session, &user.Email, &user.Avatar, &user.Message, &user.URLPrefix, &user.URLName, &user.Level, &user.Score, &user.Last_IP)
+ return &user, err
+}
+
+// Placeholder methods, the actual queries are done elsewhere
+func (sus *SqlUserStore) Add(item *User) error {
+ return nil
+}
+func (sus *SqlUserStore) AddUnsafe(item *User) error {
+ return nil
+}
+func (sus *SqlUserStore) Remove(id int) error {
+ return nil
+}
+func (sus *SqlUserStore) RemoveUnsafe(id int) error {
+ return nil
+}
+func (sus *SqlUserStore) GetCapacity() int {
+ return 0
+}
+
+func (sus *SqlUserStore) GetLength() int {
+ // Return the total number of users registered on the forums
+ return 0
+}
+
func SetPassword(uid int, password string) (error) {
salt, err := GenerateSafeString(saltLength)
if err != nil {
@@ -128,38 +284,40 @@ func SessionCheck(w http.ResponseWriter, r *http.Request) (user User, noticeList
return user, noticeList, success
}
-func SimpleSessionCheck(w http.ResponseWriter, r *http.Request) (user User, success bool) {
+func SimpleSessionCheck(w http.ResponseWriter, r *http.Request) (User,bool) {
// Are there any session cookies..?
cookie, err := r.Cookie("uid")
if err != nil {
- user.Group = 6
- user.Perms = GuestPerms
- return user, true
+ return User{ID:0,Group:6,Perms:GuestPerms}, true
}
- user.ID, err = strconv.Atoi(cookie.Value)
+ uid, err := strconv.Atoi(cookie.Value)
if err != nil {
- user.Group = 6
- user.Perms = GuestPerms
- return user, true
+ return User{ID:0,Group:6,Perms:GuestPerms}, true
}
cookie, err = r.Cookie("session")
if err != nil {
- user.Group = 6
- user.Perms = GuestPerms
- return user, true
+ return User{ID:0,Group:6,Perms:GuestPerms}, true
}
// Is this session valid..?
- err = get_session_stmt.QueryRow(user.ID,cookie.Value).Scan(&user.ID, &user.Name, &user.Group, &user.Is_Super_Admin, &user.Session, &user.Email, &user.Avatar, &user.Message, &user.URLPrefix, &user.URLName, &user.Level, &user.Score, &user.Last_IP)
+ user, err := users.CascadeGet(uid)
if err == sql.ErrNoRows {
user.ID = 0
user.Session = ""
user.Group = 6
user.Perms = GuestPerms
- return user, true
+ return *user, true
} else if err != nil {
InternalError(err,w,r)
- return user, false
+ return *user, false
+ }
+
+ if user.Session == "" || cookie.Value != user.Session {
+ user.ID = 0
+ user.Session = ""
+ user.Group = 6
+ user.Perms = GuestPerms
+ return *user, true
}
user.Is_Admin = user.Is_Super_Admin || groups[user.Group].Is_Admin
@@ -187,17 +345,17 @@ func SimpleSessionCheck(w http.ResponseWriter, r *http.Request) (user User, succ
host, _, err := net.SplitHostPort(r.RemoteAddr)
if err != nil {
- LocalError("Bad IP",w,r,user)
- return user, false
+ PreError("Bad IP",w,r)
+ return *user, false
}
if host != user.Last_IP {
_, err = update_last_ip_stmt.Exec(host, user.ID)
if err != nil {
InternalError(err,w,r)
- return user, false
+ return *user, false
}
}
- return user, true
+ return *user, true
}
func words_to_score(wcount int, topic bool) (score int) {