//MsgHistChannel get message history of a channel //It will fetch message history from the latest to oldest func MsgHistChannel(em *EndptMsg, ws *websocket.Conn) { channel, ok := em.GetDataString("channel") if !ok { log.Error("MsgHistChannel() null channel") return } log.Debug("[MsgHistChannel] userId=" + em.UserID + ".channel = " + channel) i := 0 for { var res []MessageHist query := bson.M{"userId": em.UserID, "target": channel} err := DBQueryArr("ircboks", "msghist", query, "-timestamp", 50, 50*i, &res) if err != nil { log.Error("[MsgHistChannel]fetching channel history:" + err.Error()) return } m := map[string]interface{}{ "logs": res, "channel": channel, } //send the result websocket.Message.Send(ws, jsonMarshal("msghistChannel", m)) if len(res) == 0 || res[len(res)-1].ReadFlag == true { break } i = i + 1 } }
// serveWeb starts a webserver that can serve a page and websocket events as // they are seen. // It is expected to be run only once and uses the http package global request // router. It does NOT return. func serveWeb(port uint16, events chan *gitsync.GitChange) { // the container for websocket clients, passed into every websocket handler // below var cs = clientSet{ clients: make(map[*websocket.Conn]chan gitsync.GitChange)} // Handle any static files (JS/CSS files) if handler, err := webcontent.NewMapHandler(webcontent.Paths); err != nil { log.Error("Error building templates for web content: %s", err) } else { log.Debug("Registering / handler") http.Handle("/", handler) } // Events endpoint // Note: we wrap the handler in a closure to pass in the clientSet http.Handle("/events", websocket.Handler(func(ws *websocket.Conn) { handleGitChangeWebClient(&cs, ws) })) log.Info("Attempting to spawn webserver on %d", port) go cs.distribute(events) if err := http.ListenAndServe(fmt.Sprintf(":%v", port), nil); err != nil { log.Error("Error listening on %d: %s", port, err) } }
//UserRegister handle user registration func UserRegister(e *EndptMsg, ws *websocket.Conn) { userID := e.UserID password, ok := e.GetDataString("password") if !ok { websocket.Message.Send(ws, `{"event":"registrationResult", "data" : {"result":"failed", "reason":null password"}}`) return } //check if user already exist if isUserExist(userID) { log.Info("[registerUser]User '" + userID + "' already registered") websocket.Message.Send(ws, `{"event":"registrationResult", "data" : {"result":"failed", "reason":"email address already registered"}}`) return } log.Info("[registerUser] registering " + userID) hashedPass, err := authHassPassword(password) if err != nil { log.Error("[RegisterUser]:failed to hass password : "******"event":"registrationResult", "data" : {"result":"failed", "reason":"internal error"}}`) return } if len(hashedPass) == 0 { log.Error("[RegisterUser]:failed to hass password : password len = 0") websocket.Message.Send(ws, `{"event":"registrationResult", "data" : {"result":"failed", "reason":"invalid password"}}`) return } log.Debug("generated password = "******"mongodb_uri") sess, err := mgo.Dial(uri) if err != nil { log.Error("Can't connect to mongo, go error :" + err.Error()) websocket.Message.Send(ws, `{"event":"registrationResult", "data" : {"result":"failed", "reason":"internal DB error"}}`) return } defer sess.Close() sess.SetSafe(&mgo.Safe{}) collection := sess.DB("ircboks").C("user") doc := AuthInfo{bson.NewObjectId(), userID, hashedPass} err = collection.Insert(doc) if err != nil { log.Error("Can't insert new user:"******"event":"registrationResult", "data" : {"result":"failed", "reason":"internal DB error"}}`) return } websocket.Message.Send(ws, `{"event":"registrationResult", "data" : {"result":"ok"}}`) }
//Part or leave a channel func (c *IRCClient) Part(em *EndptMsg) { if len(em.Args) == 0 { log.Error("part() invalid args len = 0") return } c.client.Part(em.Args[0], "") }
//handle IRCBoks message func dispatchBoksHandler(wsCtx *wsContext, em *EndptMsg) { resp := "{}" if em.Event == "login" { resp, isLoginOK, _ := UserLogin(em, wsCtx.Ws) wsCtx.LoggedIn = isLoginOK websocket.Message.Send(wsCtx.Ws, resp) return } else if em.Event == "userRegister" { UserRegister(em, wsCtx.Ws) return } if !wsCtx.LoggedIn { em := EndptMsg{ Event: "illegalAccess", Data: map[string]interface{}{ "reason": "needLogin", }, } websocket.Message.Send(wsCtx.Ws, em.Marshal()) } if fn, ok := boksHandlers[em.Event]; ok { go fn(em, wsCtx.Ws) } else { log.Error("dispatchBoksHandler() unhandled event:" + em.Event) } }
//processIrcMsg will unmarshal irc command json string and dispatch it to respective handler func (c *IRCClient) processIrcMsg(em *EndptMsg) { //handler for IRC Command handlers := map[string]func(*EndptMsg){ "join": c.Join, "privmsg": c.PrivMsg, "part": c.Part, "names": c.Names, "nick": c.Nick, "topic": c.Topic, } if fn, ok := handlers[em.Event]; ok { fn(em) return } switch em.Event { case "ircBoxInfo": info := c.dumpInfo() EndpointPublishID(em.UserID, info) case "killMe": c.client.Stop() go func() { c.stopChan <- true }() default: log.Error("processIrcMessage() unknown command :" + em.Event) } }
//DBUpdateOne update a doc func DBUpdateOne(dbName, colName, oid string, updateQuery bson.M) error { sess, err := getSession() if err != nil { log.Error("[DBUpdateOne]failed to connect to server :" + err.Error()) return nil } defer sess.Close() sess.SetSafe(&mgo.Safe{}) err = sess.DB(dbName).C(colName).Update(bson.M{"_id": bson.ObjectIdHex(oid)}, updateQuery) if err != nil { log.Error("[DBUpdateOne]dbName = " + dbName + ".collection = " + colName + ".oid = " + oid + ". err = " + err.Error()) } return err }
//Nick change nick of this client func (c *IRCClient) Nick(em *EndptMsg) { if newNick, ok := em.GetDataString("new_nick"); ok { c.client.SetNick(newNick) } else { log.Error("SetNick() empty nick") } }
func msgHistNick(userID, nick string, ws *websocket.Conn) { i := 0 query1 := bson.M{"userId": userID, "nick": nick, "to_channel": false} //message from this nick, not in channel query2 := bson.M{"userId": userID, "target": nick} //message to this nick query := bson.M{"$or": []bson.M{query1, query2}} for { var hists []MessageHist err := DBQueryArr("ircboks", "msghist", query, "-timestamp", 50, 50*i, &hists) if err != nil { log.Error("[MsgHistNick]fetching channel nick:" + err.Error()) return } m := map[string]interface{}{ "logs": hists, "nick": nick, } //send it back websocket.Message.Send(ws, jsonMarshal("msghistNickResp", m)) if len(hists) == 0 || hists[len(hists)-1].ReadFlag == true { break } i++ } }
//websocket main handler func wsMain(ws *websocket.Conn) { defer ws.Close() wsCtx := newWSContext(ws) var msg string for { //read message err := websocket.Message.Receive(ws, &msg) if err != nil { break } log.Debug("[wsMain]endpoint's msg = " + msg) //parse message wsMsg, err := NewEndptMsgFromStr(msg) if err != nil { log.Error("[wsMain]failed to unmarshal json :" + err.Error()) continue } wsCtx.UserID = wsMsg.UserID if wsMsg.Domain == "irc" && wsCtx.LoggedIn { ClientDoIRCCmd(wsMsg, ws) } else { dispatchBoksHandler(wsCtx, wsMsg) } } if wsCtx.LoggedIn { UserLogout(wsCtx.UserID, ws) } log.Debug("[wsMain]endpoint exited") }
//UserLogout will log out the user func UserLogout(userID string, ws *websocket.Conn) { ctx, found := ContextMap.Get(userID) if !found { log.Error("[UserLogout]can't find = " + userID) return } ctx.DelWs(ws) }
//ClientDoIRCCmd receive IRC command and run it func ClientDoIRCCmd(em *EndptMsg, ws *websocket.Conn) { ctx, found := ContextMap.Get(em.UserID) if !found { log.Error("Can't find client ctx for userId = " + em.UserID) return } ctx.InChan <- em }
//MsgHistNick get message history of a nick func MsgHistNick(em *EndptMsg, ws *websocket.Conn) { nick, ok := em.GetDataString("nick") if !ok { log.Error("MsgHistNick() empty nick") return } msgHistNick(em.UserID, nick, ws) }
//DBSelectDistinct do similar operation like 'select ... distinct ...' on SQL func DBSelectDistinct(dbName, colName string, query bson.M, distinctBy string, res interface{}) error { sess, err := getSession() if err != nil { log.Error("[DBSelectDistinct]Error getSession:", err.Error()) return err } defer sess.Close() return sess.DB(dbName).C(colName).Find(query).Distinct(distinctBy, res) }
//process JOIN event func (c *IRCClient) processJoined(e *ogric.Event) { if len(e.Arguments) == 0 { log.Error("processJoined() invalid event args len = 0") } else { log.Debug("process join nick=" + e.Nick) channelName := e.Arguments[0] c.chanJoinedSet[channelName] = true } c.forwardEvent(e) }
//DBQueryArr retrieve array of document from mongodb server func DBQueryArr(dbName, colName string, query bson.M, sortStr string, limit, skip int, res interface{}) error { sess, err := getSession() if err != nil { log.Error("[DBQueryArr]Can't connect to mongo. error:", err.Error()) return err } defer sess.Close() return sess.DB(dbName).C(colName).Find(query).Sort(sortStr).Skip(skip).Limit(limit).All(res) }
//jsonMarshal marshal map to json string //this function is deprecated, we should use EndptMsg.MarshalJson func jsonMarshal(event string, data map[string]interface{}) string { em := &EndptMsg{Event: event, Data: data} b, err := json.Marshal(em) if err != nil { log.Error("jsonMarshal() failed. err =", err.Error()) return "{}" } return string(b) }
//getSession return cloned session of mongodb connection. //It will create the connection when needed func getSession() (*mgo.Session, error) { var err error if mgoSes == nil { mgoSes, err = mgo.Dial(Config.GetString("mongodb_uri")) if err != nil { log.Error("failed to connect to mongodb :" + err.Error()) return nil, err } } return mgoSes.Clone(), nil }
//MsgHistMarkRead mark messages readFlag as read func MsgHistMarkRead(em *EndptMsg, ws *websocket.Conn) { oids := em.Args if len(oids) == 0 { log.Error("MsgHistMarkRead() empty oids") return } for _, oid := range oids { updQuery := bson.M{"$set": bson.M{"read_flag": true}} DBUpdateOne("ircboks", "msghist", oid, updQuery) } }
//process PART func (c *IRCClient) processPart(e *ogric.Event) { if len(e.Arguments) == 0 { log.Error("processPart() invalid event args len = 0") } else if e.Nick == c.nick { channelName := e.Arguments[0] c.chanJoinedSet[channelName] = true if _, exist := c.chanJoinedSet[channelName]; exist { delete(c.chanJoinedSet, channelName) } } c.forwardEvent(e) }
//MsgHistInsert save a message to DB func MsgHistInsert(userID, target, nick, message string, timestamp int64, readFlag, incoming bool) bson.ObjectId { objectID := bson.NewObjectId() toChannel := false if string(target[0]) == "#" { toChannel = true } doc := MessageHist{objectID, userID, target, nick, message, timestamp, readFlag, toChannel, incoming} err := DBInsert("ircboks", "msghist", &doc) if err != nil { log.Error("[insertMsgHistory] failed : " + err.Error()) } return objectID }
//MsgHistNicksUnread get all unread messages that is not from channel func MsgHistNicksUnread(em *EndptMsg, ws *websocket.Conn) { var unreadNicks []string query := bson.M{"userId": em.UserID, "to_channel": false, "incoming": true, "read_flag": false} if err := DBSelectDistinct("ircboks", "msghist", query, "nick", &unreadNicks); err != nil { log.Error("MsgHistNicksUnread:selecr distinct err :" + err.Error()) return } m := map[string]interface{}{ "nicks": unreadNicks, } websocket.Message.Send(ws, jsonMarshal("msghistNicksUnread", m)) }
//ClientDestroy kill the client func ClientDestroy(em *EndptMsg, ws *websocket.Conn) { ctx, found := ContextMap.Get(em.UserID) if !found { log.Error("Can't find client ctx for userId = " + em.UserID) return } ctx.InChan <- em ContextMap.Del(em.UserID) em = &EndptMsg{Event: "ircClientDestroyed"} jsonStr := em.Marshal() websocket.Message.Send(ws, jsonStr) }
//DBGetOne retrieve a document from DB func DBGetOne(dbName, colName string, bsonM bson.M, doc interface{}) error { sess, err := getSession() if err != nil { log.Error("[DBGetOne]failed to connect to server :" + err.Error()) return nil } defer sess.Close() col := sess.DB(dbName).C(colName) err = col.Find(bsonM).One(doc) if err != nil { //TODO : handle in case error is not "not found" error return err } return nil }
//EndpointPublisher is func EndpointPublisher() { for { msg := <-endpointMsgChan if msg.ClientCtx == nil { ctx, found := ContextMap.Get(msg.UserID) if !found { log.Error("[EndpointWriter] failed to find context for :" + msg.UserID) continue } msg.ClientCtx = ctx } for _, ws := range msg.ClientCtx.wsArr { websocket.Message.Send(ws, msg.Msg) } } }
func main() { log.LoadConfiguration("timber.xml") r := mux.NewRouter() r.Handle("/irc/", websocket.Handler(wsMain)) r.PathPrefix("/").Handler(http.FileServer(http.Dir("../ui/build/"))) ContextMapInit() go EndpointPublisher() log.Debug("Starting ircboks server ..") if err := http.ListenAndServe(Config.GetString("host_port"), r); err != nil { log.Error("ListenAndServer error :", err.Error()) } log.Close() }
// handleGitChangeWebClient will build and register a channel to revieve events // on from a distributor in clientSet. It will exit on error but will consume // one more event after a client disconnects before it does so. func handleGitChangeWebClient(cs *clientSet, ws *websocket.Conn) { var events = make(chan gitsync.GitChange) log.Info("Begin handling %s", makeWebsocketName(ws)) defer log.Info("End handling %s", makeWebsocketName(ws)) cs.AddClient(ws, events) defer cs.RemoveClient(ws) for event := range events { if data, err := json.Marshal(event); err != nil { log.Info("%s: Cannot marshall event data %+v. %s", makeWebsocketName(ws), event, err) } else { if _, err := ws.Write(data); err != nil { ws.Close() log.Error("%s: Cannot write out event: %s", makeWebsocketName(ws), err) return } else { log.Info("%s: Wrote out data", makeWebsocketName(ws)) } } } }
func (log TimberLog) Warn(v ...interface{}) { tl.Error("", v) }
func (log TimberLog) Info(v ...interface{}) { tl.Error("", v) }