From 206775fa6adbbf9f07037a293bdcbde5d6d18ea6 Mon Sep 17 00:00:00 2001 From: Las Zenow Date: Mon, 22 Apr 2013 23:28:00 +0200 Subject: [PATCH] Store statistics on mongodb --- admin.go | 18 ++++------ config.go | 3 ++ database.go | 6 ++++ reader.go | 14 ++++---- search.go | 2 +- session.go | 11 ++++++ stats.go | 97 +++++++++++++++++++++++++++++++++++++++++++++++++++++ trantor.go | 52 ++++++++++++++-------------- upload.go | 8 ++--- 9 files changed, 159 insertions(+), 52 deletions(-) create mode 100644 stats.go diff --git a/admin.go b/admin.go index cbcde5f..b06c88d 100644 --- a/admin.go +++ b/admin.go @@ -13,8 +13,7 @@ type settingsData struct { S Status } -func settingsHandler(w http.ResponseWriter, r *http.Request) { - sess := GetSession(r) +func settingsHandler(w http.ResponseWriter, r *http.Request, sess *Session) { if sess.User == "" { http.NotFound(w, r) return @@ -39,8 +38,7 @@ func settingsHandler(w http.ResponseWriter, r *http.Request) { loadTemplate(w, "settings", data) } -func deleteHandler(w http.ResponseWriter, r *http.Request) { - sess := GetSession(r) +func deleteHandler(w http.ResponseWriter, r *http.Request, sess *Session) { if sess.User == "" { http.NotFound(w, r) return @@ -80,8 +78,7 @@ func deleteHandler(w http.ResponseWriter, r *http.Request) { } } -func editHandler(w http.ResponseWriter, r *http.Request) { - sess := GetSession(r) +func editHandler(w http.ResponseWriter, r *http.Request, sess *Session) { if sess.User == "" { http.NotFound(w, r) return @@ -109,8 +106,7 @@ func cleanEmptyStr(s []string) []string { return res } -func saveHandler(w http.ResponseWriter, r *http.Request) { - sess := GetSession(r) +func saveHandler(w http.ResponseWriter, r *http.Request, sess *Session) { if sess.User == "" { http.NotFound(w, r) return @@ -162,8 +158,7 @@ type newData struct { Prev string } -func newHandler(w http.ResponseWriter, r *http.Request) { - sess := GetSession(r) +func newHandler(w http.ResponseWriter, r *http.Request, sess *Session) { if sess.User == "" { http.NotFound(w, r) return @@ -206,8 +201,7 @@ func newHandler(w http.ResponseWriter, r *http.Request) { loadTemplate(w, "new", data) } -func storeHandler(w http.ResponseWriter, r *http.Request) { - sess := GetSession(r) +func storeHandler(w http.ResponseWriter, r *http.Request, sess *Session) { if sess.User == "" { http.NotFound(w, r) return diff --git a/config.go b/config.go index cb5df16..8ed9110 100644 --- a/config.go +++ b/config.go @@ -9,6 +9,7 @@ const ( BOOKS_COLL = "books" TAGS_COLL = "tags" USERS_COLL = "users" + STATS_COLL = "statistics" FS_BOOKS = "fs_books" FS_IMGS = "fs_imgs" @@ -26,4 +27,6 @@ const ( IMG_WIDTH_BIG = 300 IMG_WIDTH_SMALL = 60 IMG_QUALITY = 80 + + STATS_CHAN_SIZE = 100 ) diff --git a/database.go b/database.go index 9dd9127..32003cc 100644 --- a/database.go +++ b/database.go @@ -44,6 +44,7 @@ type DB struct { books *mgo.Collection tags *mgo.Collection user *mgo.Collection + stats *mgo.Collection } func initDB() *DB { @@ -59,6 +60,7 @@ func initDB() *DB { d.books = database.C(BOOKS_COLL) d.tags = database.C(TAGS_COLL) d.user = database.C(USERS_COLL) + d.stats = database.C(STATS_COLL) return d } @@ -86,6 +88,10 @@ func (d *DB) UserValid(user string, pass string) bool { return n != 0 } +func (d *DB) InsertStats(stats interface{}) error { + return d.stats.Insert(stats) +} + func (d *DB) InsertBook(book interface{}) error { return d.books.Insert(book) } diff --git a/reader.go b/reader.go index 4429aac..ed035e5 100644 --- a/reader.go +++ b/reader.go @@ -127,9 +127,9 @@ func listChapters(nav *epubgo.NavigationIterator, depth int) []chapter { return chapters } -func readStartHandler(w http.ResponseWriter, r *http.Request) { +func readStartHandler(w http.ResponseWriter, r *http.Request, sess *Session) { id := mux.Vars(r)["id"] - e, _ := openReadEpub(w, r) + e, _ := openReadEpub(w, r, sess) if e == nil { http.NotFound(w, r) return @@ -144,10 +144,10 @@ func readStartHandler(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, "/read/"+id+"/"+it.Url(), http.StatusTemporaryRedirect) } -func readHandler(w http.ResponseWriter, r *http.Request) { +func readHandler(w http.ResponseWriter, r *http.Request, sess *Session) { id := mux.Vars(r)["id"] file := mux.Vars(r)["file"] - e, book := openReadEpub(w, r) + e, book := openReadEpub(w, r, sess) if e == nil { http.NotFound(w, r) return @@ -169,7 +169,7 @@ func readHandler(w http.ResponseWriter, r *http.Request) { loadTemplate(w, "read", data) } -func openReadEpub(w http.ResponseWriter, r *http.Request) (*epubgo.Epub, Book) { +func openReadEpub(w http.ResponseWriter, r *http.Request, sess *Session) (*epubgo.Epub, Book) { var book Book id := mux.Vars(r)["id"] books, _, err := db.GetBooks(bson.M{"_id": bson.ObjectIdHex(id)}) @@ -179,7 +179,6 @@ func openReadEpub(w http.ResponseWriter, r *http.Request) (*epubgo.Epub, Book) { book = books[0] if !book.Active { - sess := GetSession(r) if sess.User == "" { return nil, book } @@ -191,7 +190,7 @@ func openReadEpub(w http.ResponseWriter, r *http.Request) (*epubgo.Epub, Book) { return e, book } -func contentHandler(w http.ResponseWriter, r *http.Request) { +func contentHandler(w http.ResponseWriter, r *http.Request, sess *Session) { vars := mux.Vars(r) id := vars["id"] file := vars["file"] @@ -207,7 +206,6 @@ func contentHandler(w http.ResponseWriter, r *http.Request) { } book := books[0] if !book.Active { - sess := GetSession(r) if sess.User == "" { http.NotFound(w, r) return diff --git a/search.go b/search.go index 02fdaa1..61e20e7 100644 --- a/search.go +++ b/search.go @@ -34,7 +34,7 @@ type searchData struct { Prev string } -func searchHandler(w http.ResponseWriter, r *http.Request) { +func searchHandler(w http.ResponseWriter, r *http.Request, sess *Session) { err := r.ParseForm() if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) diff --git a/session.go b/session.go index 04fe668..bf861fc 100644 --- a/session.go +++ b/session.go @@ -1,6 +1,7 @@ package main import ( + "encoding/hex" "github.com/gorilla/securecookie" "github.com/gorilla/sessions" "net/http" @@ -42,6 +43,11 @@ func GetSession(r *http.Request) (s *Session) { s.User, _ = s.S.Values["user"].(string) s.Notif = getNotif(s.S) } + + if s.S.IsNew { + s.S.Values["id"] = hex.EncodeToString(securecookie.GenerateRandomKey(16)) + } + return } @@ -63,3 +69,8 @@ func (s *Session) Notify(title, msg, tpe string) { func (s *Session) Save(w http.ResponseWriter, r *http.Request) { sesStore.Save(r, w, s.S) } + +func (s *Session) Id() string { + id, _ := s.S.Values["id"].(string) + return id +} diff --git a/stats.go b/stats.go new file mode 100644 index 0000000..2c35875 --- /dev/null +++ b/stats.go @@ -0,0 +1,97 @@ +package main + +import ( + "github.com/gorilla/mux" + "labix.org/v2/mgo/bson" + "net/http" + "strings" + "time" +) + +func InitStats() { + statsChannel = make(chan statsRequest, STATS_CHAN_SIZE) + go statsWorker() +} + +func GatherStats(function func(http.ResponseWriter, *http.Request, *Session)) func(http.ResponseWriter, *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + sess := GetSession(r) + function(w, r, sess) + statsChannel <- statsRequest{bson.Now(), mux.Vars(r), sess, r} + } +} + +var statsChannel chan statsRequest + +type statsRequest struct { + date time.Time + vars map[string]string + sess *Session + r *http.Request +} + +func statsWorker() { + for req := range statsChannel { + stats := make(map[string]interface{}) + appendFiles(req.r, stats) + appendMuxVars(req.vars, stats) + appendUrl(req.r, stats) + appendSession(req.sess, stats) + stats["method"] = req.r.Method + stats["date"] = req.date + db.InsertStats(stats) + } +} + +func appendFiles(r *http.Request, stats map[string]interface{}) { + if r.Method == "POST" && r.MultipartForm.File != nil { //FIXM + files := r.MultipartForm.File + for key := range files { + list := make([]string, len(files[key])) + for i, f := range files[key] { + list[i] = f.Filename + } + stats[key] = list + } + } +} + +func appendMuxVars(vars map[string]string, stats map[string]interface{}) { + for key, value := range vars { + switch { + case key == "id": + stats["id"] = bson.ObjectIdHex(value) + case key == "ids": + ids := strings.Split(value, "/") + objectIds := make([]bson.ObjectId, len(ids)) + for i, id := range ids { + objectIds[i] = bson.ObjectIdHex(id) + } + stats["ids"] = objectIds + stats["id"] = objectIds[0] + default: + stats[key] = value + } + } +} + +func appendUrl(r *http.Request, stats map[string]interface{}) { + for key, value := range r.URL.Query() { + stats[key] = value + } + stats["host"] = r.Host + stats["path"] = r.URL.Path + pattern := strings.Split(r.URL.Path, "/") + if len(pattern) > 1 && pattern[1] != "" { + stats["section"] = pattern[1] + } else { + stats["section"] = "/" + } +} + +func appendSession(sess *Session, stats map[string]interface{}) { + stats["session"] = sess.Id() + if sess.User != "" { + stats["user"] = sess.User + } +} diff --git a/trantor.go b/trantor.go index 00e7053..7371ea0 100644 --- a/trantor.go +++ b/trantor.go @@ -12,15 +12,14 @@ type aboutData struct { S Status } -func aboutHandler(w http.ResponseWriter, r *http.Request) { +func aboutHandler(w http.ResponseWriter, r *http.Request, sess *Session) { var data aboutData data.S = GetStatus(w, r) data.S.About = true loadTemplate(w, "about", data) } -func logoutHandler(w http.ResponseWriter, r *http.Request) { - sess := GetSession(r) +func logoutHandler(w http.ResponseWriter, r *http.Request, sess *Session) { sess.LogOut() sess.Notify("Log out!", "Bye bye "+sess.User, "success") sess.Save(w, r) @@ -28,10 +27,9 @@ func logoutHandler(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, "/", http.StatusFound) } -func loginHandler(w http.ResponseWriter, r *http.Request) { +func loginHandler(w http.ResponseWriter, r *http.Request, sess *Session) { user := r.FormValue("user") pass := r.FormValue("pass") - sess := GetSession(r) if db.UserValid(user, pass) { log.Println("User", user, "log in") sess.LogIn(user) @@ -49,7 +47,7 @@ type bookData struct { Book Book } -func bookHandler(w http.ResponseWriter, r *http.Request) { +func bookHandler(w http.ResponseWriter, r *http.Request, sess *Session) { var data bookData data.S = GetStatus(w, r) id := bson.ObjectIdHex(mux.Vars(r)["id"]) @@ -63,7 +61,7 @@ func bookHandler(w http.ResponseWriter, r *http.Request) { loadTemplate(w, "book", data) } -func downloadHandler(w http.ResponseWriter, r *http.Request) { +func downloadHandler(w http.ResponseWriter, r *http.Request, sess *Session) { id := bson.ObjectIdHex(mux.Vars(r)["id"]) books, _, err := db.GetBooks(bson.M{"_id": id}) if err != nil || len(books) == 0 { @@ -105,7 +103,7 @@ type indexData struct { Tags []string } -func indexHandler(w http.ResponseWriter, r *http.Request) { +func indexHandler(w http.ResponseWriter, r *http.Request, sess *Session) { var data indexData data.Tags, _ = db.GetTags(TAGS_DISPLAY) @@ -121,31 +119,33 @@ func main() { db = initDB() defer db.Close() + InitStats() + setUpRouter() panic(http.ListenAndServe(":"+PORT, nil)) } func setUpRouter() { r := mux.NewRouter() - r.HandleFunc("/", indexHandler) - r.HandleFunc("/book/{id:[0-9a-fA-F]+}", bookHandler) - r.HandleFunc("/search/", searchHandler) - r.HandleFunc("/upload/", uploadHandler).Methods("GET") - r.HandleFunc("/upload/", uploadPostHandler).Methods("POST") - r.HandleFunc("/login/", loginHandler).Methods("POST") - r.HandleFunc("/logout/", logoutHandler) - r.HandleFunc("/new/", newHandler) - r.HandleFunc("/store/{ids:([0-9a-fA-F]+/)+}", storeHandler) - r.HandleFunc("/delete/{ids:([0-9a-fA-F]+/)+}", deleteHandler) - r.HandleFunc("/read/{id:[0-9a-fA-F]+}", readStartHandler) - r.HandleFunc("/read/{id:[0-9a-fA-F]+}/{file:.*}", readHandler) - r.HandleFunc("/content/{id:[0-9a-fA-F]+}/{file:.*}", contentHandler) - r.HandleFunc("/edit/{id:[0-9a-fA-F]+}", editHandler) - r.HandleFunc("/save/{id:[0-9a-fA-F]+}", saveHandler).Methods("POST") - r.HandleFunc("/about/", aboutHandler) - r.HandleFunc("/download/{id:[0-9a-fA-F]+}/{epub:.*}", downloadHandler) + r.HandleFunc("/", GatherStats(indexHandler)) + r.HandleFunc("/book/{id:[0-9a-fA-F]+}", GatherStats(bookHandler)) + r.HandleFunc("/search/", GatherStats(searchHandler)) + r.HandleFunc("/upload/", GatherStats(uploadHandler)).Methods("GET") + r.HandleFunc("/upload/", GatherStats(uploadPostHandler)).Methods("POST") + r.HandleFunc("/login/", GatherStats(loginHandler)).Methods("POST") + r.HandleFunc("/logout/", GatherStats(logoutHandler)) + r.HandleFunc("/new/", GatherStats(newHandler)) + r.HandleFunc("/store/{ids:([0-9a-fA-F]+/)+}", GatherStats(storeHandler)) + r.HandleFunc("/delete/{ids:([0-9a-fA-F]+/)+}", GatherStats(deleteHandler)) + r.HandleFunc("/read/{id:[0-9a-fA-F]+}", GatherStats(readStartHandler)) + r.HandleFunc("/read/{id:[0-9a-fA-F]+}/{file:.*}", GatherStats(readHandler)) + r.HandleFunc("/content/{id:[0-9a-fA-F]+}/{file:.*}", GatherStats(contentHandler)) + r.HandleFunc("/edit/{id:[0-9a-fA-F]+}", GatherStats(editHandler)) + r.HandleFunc("/save/{id:[0-9a-fA-F]+}", GatherStats(saveHandler)).Methods("POST") + r.HandleFunc("/about/", GatherStats(aboutHandler)) + r.HandleFunc("/download/{id:[0-9a-fA-F]+}/{epub:.*}", GatherStats(downloadHandler)) r.HandleFunc("/cover/{id:[0-9a-fA-F]+}/{size}/{img:.*}", coverHandler) - r.HandleFunc("/settings/", settingsHandler) + r.HandleFunc("/settings/", GatherStats(settingsHandler)) h := http.FileServer(http.Dir(IMG_PATH)) r.Handle("/img/{img}", http.StripPrefix("/img/", h)) h = http.FileServer(http.Dir(CSS_PATH)) diff --git a/upload.go b/upload.go index e4e26db..b55b498 100644 --- a/upload.go +++ b/upload.go @@ -10,9 +10,7 @@ import ( "strings" ) -func uploadPostHandler(w http.ResponseWriter, r *http.Request) { - sess := GetSession(r) - +func uploadPostHandler(w http.ResponseWriter, r *http.Request, sess *Session) { uploaded := "" r.ParseMultipartForm(20000000) filesForm := r.MultipartForm.File["epub"] @@ -49,10 +47,10 @@ func uploadPostHandler(w http.ResponseWriter, r *http.Request) { sess.Notify("Upload successful!", "Added the books:"+uploaded+". Thank you for your contribution", "success") } - uploadHandler(w, r) + uploadHandler(w, r, sess) } -func uploadHandler(w http.ResponseWriter, r *http.Request) { +func uploadHandler(w http.ResponseWriter, r *http.Request, sess *Session) { var data uploadData data.S = GetStatus(w, r) data.S.Upload = true