// handle PUT(/tab/:token/:bookid/:tabid) func changeTab(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { lib.Trace(2, "handler:changeTab start") var result d.Result bookid := ps.ByName("bookid") login := &d.Login{Token: ps.ByName("token"), Bookid: bookid, Type: "edit"} resultChan := d.Data("auth", login) if result = <-resultChan; result.Status != d.DataOk { http.Error(w, result.Val.(string), http.StatusBadRequest) return } request := new(tabRequest) if decodeErr := json.NewDecoder(r.Body).Decode(request); decodeErr != nil { http.Error(w, "Program Error: tabRequest json decode", http.StatusInternalServerError) return } // change tab tab := &d.Tab{ Id: ps.ByName("tabid"), Bookid: bookid, TabNumber: request.TabNumber, TabName: request.TabName, Hidden: request.Hidden, } resultChan = d.Data("changeTab", tab) result = <-resultChan w.Write([]byte("Update Successful")) lib.Trace(2, "handler:changeTab end") }
// handle PUT(/note/:token/:bookid/:tabid/:noteid) func changeNote(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { lib.Trace(2, "handler:changeNote start") var result d.Result bookid := ps.ByName("bookid") login := &d.Login{Token: ps.ByName("token"), Bookid: bookid, Type: "edit"} resultChan := d.Data("auth", login) if result = <-resultChan; result.Status != d.DataOk { http.Error(w, result.Val.(string), http.StatusInternalServerError) return } request := new(noteRequest) if decodeErr := json.NewDecoder(r.Body).Decode(request); decodeErr != nil { http.Error(w, "Program Error: noteRequest json decode", http.StatusInternalServerError) return } note := &d.Note{ Id: ps.ByName("noteid"), Content: request.Content, When: time.Now(), Html: request.Html, Markdown: request.Markdown, Mono: request.Mono, } noteParms := &d.NoteParms{ Bookid: bookid, Tabid: ps.ByName("tabid"), Note: note, } resultChan = d.Data("changeNote", noteParms) result = <-resultChan w.Write([]byte("Update Successful")) lib.Trace(2, "handler:changeNote end") }
func viewBook(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { lib.Trace(2, "handler:viewBook start") // get book book := &d.Book{BookName: ps.ByName("bookname")} resultChan := d.Data("getBook", book) result := <-resultChan if result.Status != d.DataOk { // resultVal contains errMsg http.Error(w, result.Val.(string), http.StatusBadRequest) return } // get book tabs bookTabs := &d.GetBookTabs{Bookid: book.Id} // see data/getbooktabs.go resultChan = d.Data("getBookTabs", bookTabs) result = <-resultChan // build response response := &loginResponse{ Bookid: book.Id, BookName: book.BookName, Tabs: bookTabs.TabInfoMap, Broadcast: getBroadcast(), } // add Login login := &d.Login{Bookid: book.Id, Type: "view"} resultChan = d.Data("addLogin", login) result = <-resultChan response.Token = login.Token jsonResponseWriter(w, r, response) lib.Trace(2, "handler:viewBook end") }
// jsonResponseWriter - convert response data to json, gzip if allowed, write to http.ResponseWriter func jsonResponseWriter(w http.ResponseWriter, r *http.Request, data interface{}) error { var err error var jsonData []byte if jsonData, err = json.Marshal(data); err != nil { return err } w.Header().Set("Content-Type", "application/json") gzipOk := strings.Contains(fmt.Sprint(r.Header["Accept-Encoding"]), "gzip") if gzipOk { w.Header().Set("Content-Encoding", "gzip") compressor := common.GzipWriterPool.Get().(*gzip.Writer) compressor.Reset(w) _, err = compressor.Write(jsonData) compressor.Close() common.GzipWriterPool.Put(compressor) lib.Trace(3, "json response gzip") } else { _, err = w.Write(jsonData) } if err != nil { lib.Trace(0, "jsonResponseWriter error: ", err.Error()) } return err }
// handle GET(/tabnotes/:token/:bookid/:tabid) func getTabNotes(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { lib.Trace(2, "handler:getTabNotes start") var result d.Result bookid := ps.ByName("bookid") login := &d.Login{Token: ps.ByName("token"), Bookid: bookid, Type: "view"} resultChan := d.Data("auth", login) if result = <-resultChan; result.Status != d.DataOk { http.Error(w, result.Val.(string), http.StatusInternalServerError) return } // get tab notes tabNotes := &d.GetTabNotes{ Bookid: bookid, Tabid: ps.ByName("tabid"), ETag: r.Header.Get("If-None-Match"), } // see data/gettabnotes.go lib.Trace(1, "Request ETag =", tabNotes.ETag) resultChan = d.Data("getTabNotes", tabNotes) result = <-resultChan if result.Status == d.DataNotChanged { w.WriteHeader(http.StatusNotModified) } else { w.Header().Set("Etag", tabNotes.ETag) jsonResponseWriter(w, r, tabNotes.Notes) // Notes = map[string]*d.NoteResponseRec } lib.Trace(2, "handler:getTabNotes end", len(tabNotes.Notes)) }
func dataDispatch() { var req request for { req = <-requestChan req.data.process(req.action, req.resultChan) lib.Trace(2, "data request done") if req.action == "shutdown" { lib.Trace(0, "data dispatch stopped") break } } }
func dbWriteDispatch() { var req request for { req = <-dbWriteChan lib.Trace(2, "dbWrite Request", req.action) req.data.process(req.action, req.resultChan) lib.Trace(2, "dbWrite Request done") if req.action == "shutdowndb" { lib.Trace(0, "dbWrite stopped") break } } }
// handles requests with URL = /shutdown/:keyword func shutdown(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { if ps.ByName("keyword") != "downboy" { http.Error(w, "Sorry Charlie", http.StatusInternalServerError) lib.Trace(0, "shutdown - invalid keyword") return } w.Write([]byte("shutdown request received")) lib.Trace(0, "shutdown request received") resultChan := d.Data("shutdown", &d.SimpleRequest{nil}) <-resultChan time.Sleep(3 * time.Second) // wait for Trace buffer to clear os.Exit(0) }
func createBook(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { lib.Trace(2, "handler:createBook start") request := new(loginRequest) if decodeErr := json.NewDecoder(r.Body).Decode(request); decodeErr != nil { http.Error(w, "Program Error: loginRequest json decode", http.StatusInternalServerError) return } // add book book := &d.Book{ BookName: ps.ByName("bookname"), AccessCode: request.AccessCode, } resultChan := d.Data("addBook", book) result := <-resultChan if result.Status != d.DataOk { // resultVal contains errMsg http.Error(w, result.Val.(string), http.StatusBadRequest) return } // add tab tab := &d.Tab{ Bookid: book.Id, TabNumber: 1, TabName: "About", Hidden: false, } resultChan = d.Data("addTab", tab) result = <-resultChan // get book tabs (will only be the 1 just added) bookTabs := &d.GetBookTabs{Bookid: book.Id} // see data/getbooktabs.go resultChan = d.Data("getBookTabs", bookTabs) result = <-resultChan // build response response := &loginResponse{ Bookid: book.Id, BookName: book.BookName, Tabs: bookTabs.TabInfoMap, Broadcast: getBroadcast(), } // add Login login := &d.Login{Bookid: book.Id, Type: "edit"} resultChan = d.Data("addLogin", login) result = <-resultChan response.Token = login.Token jsonResponseWriter(w, r, response) lib.Trace(2, "handler:createBook end") }
func Scheduler() { schedule := make([]*item, 0, 10) schedule = append(schedule, &item{ name: "backup database", whenType: atTime, when: 3, // run @ 3 am run: backupDatabase, }) /* schedule = append(schedule, &item{ name: "audit", whenType: atTime, when: 4, // run @ 4 am run: audit, }) schedule = append(schedule, &item{ name: "remove old logins", whenType: onInterval, when: 30, // run every 30 minutes run: removeOldLogins, }) */ for { time.Sleep(10 * time.Minute) now := time.Now() lib.Trace(0, "--- Scheduled Tasks ---") for _, task := range schedule { switch task.whenType { case atTime: if now.Hour() >= task.when && now.Day() != task.lastRun.Day() { lib.Trace(0, "running ", task.name) task.lastRun = time.Now() task.run() } case onInterval: minsSinceLastRun := now.Sub(task.lastRun) / time.Minute if minsSinceLastRun >= time.Duration(task.when) { lib.Trace(0, "running ", task.name) task.lastRun = time.Now() task.run() } default: log.Fatal("invalid schedule whenType - ", task.whenType) } } } }
func DataStart(dbName string) { lib.Trace(0, "Data Start Begin") dbStart(dbName) // see db.go bookMap = make(map[string]*Book) bookTabs = make(map[string]tabMap) startupDataLoad() requestChan = make(chan request, 100) go dataDispatch() lib.Trace(0, "Data Start Complete") }
func etagMatch(r *http.Request, serverETag string) bool { requestETag := r.Header.Get("If-None-Match") lib.Trace(2, "requestEtag:", requestETag, ", serverETag:", serverETag) if requestETag == serverETag { return true } return false }
func DBBkup(path string) { db.View(func(tx *bolt.Tx) error { err := tx.CopyFile(path, 0600) if err != nil { lib.Trace(0, "*** ERROR *** DB Bkup Error: ", err.Error()) } return err }) }
func dbStart(dbFile string) { lib.Trace(0, "dbStart Begin") var err error db, err = bolt.Open(dbFile, 0600, nil) if err != nil { log.Fatal("db open fail", err) } dbWriteChan = make(chan request, 100) go dbWriteDispatch() // if 1st run for db, following will create bkts for startup db.Update(func(tx *bolt.Tx) error { _, err1 := tx.CreateBucketIfNotExists(bs("books")) _, err2 := tx.CreateBucketIfNotExists(bs("control")) return lib.CheckErrs(errFatal, "db startup error", err1, err2) }) lib.Trace(0, "dbStart Complete") }
func startupDataLoad() { lib.Trace(0, "startupDataLoad Begin") db.Update(func(tx *bolt.Tx) error { // load all book docs into bookMap bktBooks := openBooks(tx) cursor := bktBooks.Cursor() for k, v := cursor.First(); k != nil; k, v = cursor.Next() { book := new(Book) if err := json.Unmarshal(v, book); err != nil { log.Fatal("startupDataLoad json Unmarshal failed", err) } lib.Trace(0, book.BookName, book.AccessCode) bookMap[book.Id] = book } bktControl := openControl(tx) sequentialid, _ = bktControl.NextSequence() return nil }) lib.Trace(0, "startupDataLoad End, sequentialid =", sequentialid) }
func (this *SimpleRequest) process(action string, resultChan chan Result) { switch action { case "shutdown": dbWrite("shutdowndb", this, resultChan) // clear db write buffer case "shutdowndb": db.Close() lib.Trace(0, "db closed") resultChan <- Result{DataOk, nil} log.Println("shutdown done") } }
// WebStart - setup handlers and start ListenAndServ func WebStart(httpPort string) { lib.Trace(0, "WebStart") loadIndexHtml() gzipCodeJS() router := httprouter.New() router.GET("/book/:bookname", returnIndexHtml) // user specified URL router.GET("/code.js", returnCodeJS) // all js code, gzipped // handlers in bookhandlers.go router.POST("/open/:bookname", openBook) router.GET("/view/:bookname", viewBook) router.POST("/create/:bookname", createBook) // handlers in tabhandlers.go router.POST("/tab/:token/:bookid", addTab) router.PUT("/tab/:token/:bookid/:tabid", changeTab) router.GET("/tabnotes/:token/:bookid/:tabid", getTabNotes) // handlers in notehandlers.go router.POST("/note/:token/:bookid/:tabid", addNote) router.PUT("/note/:token/:bookid/:tabid/:noteid", changeNote) router.PUT("/positionnote/:token/:bookid/:tabid/:noteid", positionNote) router.DELETE("/note/:token/:bookid/:tabid/:noteid", deleteNote) // handlers in specialhandlers.go router.GET("/accesscode", getAccessCode) router.GET("/shutdown/:keyword", shutdown) router.NotFound = http.FileServer(http.Dir("static")) lib.Trace(0, "handlers assigned - listening on ", httpPort) if httpPort == ":https" { log.Fatal(http.ListenAndServeTLS(httpPort, "tls/cert.pem", "tls/key.pem", router)) } else { log.Fatal(http.ListenAndServe(httpPort, router)) } }
// handle DELETE(/note/:token/:bookid/:tabid/:noteid) func deleteNote(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { lib.Trace(2, "handler:deleteNote start") var result d.Result bookid := ps.ByName("bookid") login := &d.Login{Token: ps.ByName("token"), Bookid: bookid, Type: "edit"} resultChan := d.Data("auth", login) if result = <-resultChan; result.Status != d.DataOk { http.Error(w, result.Val.(string), http.StatusInternalServerError) return } note := &d.Note{Id: ps.ByName("noteid")} noteParms := &d.NoteParms{ Bookid: bookid, Tabid: ps.ByName("tabid"), Note: note, } resultChan = d.Data("deleteNote", noteParms) result = <-resultChan w.Write([]byte("Update Successful")) lib.Trace(2, "handler:deleteNote end") }
func (this *GetTabNotes) process(action string, resultChan chan Result) { tab := bookTabs[this.Bookid][this.Tabid] lib.Trace(1, "Tab ETag =", tab.ETag) if tab.ETag == this.ETag { resultChan <- Result{DataNotChanged, nil} return } this.ETag = tab.ETag // loaded into response header this.Notes = make(map[string]*NoteResponseRec) previds := tabPrevids[this.Tabid] // tabPrevids map kept updated, so db is not accessed db.View(func(tx *bolt.Tx) error { bktBook := openBook(tx, this.Bookid) bktTab := openTab(bktBook, this.Tabid) bktNotes := openNotes(bktTab) if bktNotes == nil { log.Fatal("GetTabNotes bkt problem (bookid, tabid)", this.Bookid, this.Tabid) } cursor := bktNotes.Cursor() for k, v := cursor.First(); k != nil; k, v = cursor.Next() { note := new(Note) if err := json.Unmarshal(v, note); err != nil { log.Fatal("GetTabNotes json Unmarshal failed", err) } dte := date.TimeToSimpleDate(note.When) this.Notes[note.Id] = &NoteResponseRec{ Content: note.Content, When: date.SimpleDateToArray(dte), Mono: note.Mono, Html: note.Html, Markdown: note.Markdown, Previd: previds[note.Id], } } return nil }) resultChan <- Result{DataOk, nil} }
// Data - called by web handlers to access data // places data request on requestChan which is processed by dataDispatch goroutine (see above) // requester receives unique result channel // data.Processor method is sent this channel which places result on it func Data(action string, data processor) chan Result { lib.Trace(2, "data request received", action) resultChan := make(chan Result) requestChan <- request{action, data, resultChan} return resultChan }
func (this *Book) process(action string, resultChan chan Result) { switch action { // getBook options: // if Id is loaded, match on Id // if AccessCode is loaded, match on AccessCode & BookName // if only BookName is loaded, match on BookName case "getBook": // all book docs loaded into bookMap at startup var book *Book if this.Id != "" { // match on book id book = bookMap[this.Id] } else if this.AccessCode != "" { // match on AccessCode and BookName for _, v := range bookMap { if v.AccessCode == this.AccessCode { if v.PlainBookName == lib.PlainString(this.BookName) { book = v break } } } } else { // match on BookName matchBookName := lib.PlainString(this.BookName) for _, v := range bookMap { if v.PlainBookName == matchBookName { book = v break } } } if book != nil { this.Id = book.Id this.BookName = book.BookName this.Email = book.Email this.AccessCode = book.AccessCode this.PlainBookName = book.PlainBookName resultChan <- Result{DataOk, nil} } else { resultChan <- Result{DataNotFound, "Book Not Found"} } case "addBook": // check for dupe book name newName := lib.PlainString(this.BookName) for _, v := range bookMap { if newName == v.PlainBookName { resultChan <- Result{DataDuplicate, "Duplicate - Notebook Name Already Exists"} lib.Trace(0, "dupe book name", newName) return } } this.Id = getNextid() this.PlainBookName = newName bookMap[this.Id] = this bookTabs[this.Id] = make(tabMap) dbWrite("saveNewBook", this, resultChan) // save new Book to db case "saveNewBook": var err, err1, err2, err3 error var k, v []byte tx, _ := db.Begin(true) bumpSequentialid(tx) // required for all db adds // --- save book to "books" bkt ----------------------- bktBooks := openBooks(tx) k = bs(this.Id) v, err1 = json.Marshal(this) err2 = bktBooks.Put(k, v) // --- create bkt to hold this book's data & Commit --- tx.CreateBucket(bs("book_" + this.Id)) err3 = tx.Commit() if err = lib.CheckErrs(errNotFatal, action, err1, err2, err3); err != nil { tx.Rollback() log.Fatal("Fatal DB Error-creating book bkts, Ending Program") } // --- create "tabs" bkt inside this book's bkt & Commit --- tx, _ = db.Begin(true) bktBook := openBook(tx, this.Id) _, err1 = bktBook.CreateBucket(bs("tabs")) err2 = tx.Commit() if err = lib.CheckErrs(errNotFatal, action, err1, err2); err != nil { tx.Rollback() log.Fatal("Fatal DB Error-creating tabs bkt, Ending Program") } resultChan <- Result{DataOk, this.Id} case "saveBookChange": // not currently used db.Update(func(tx *bolt.Tx) error { bktBooks := openBooks(tx) k := bs(this.Id) v, _ := json.Marshal(this) err := bktBooks.Put(k, v) if err != nil { log.Fatal("book not changed", err) } return nil }) default: log.Fatal("Book.process action invalid", action) } }