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