Commits

Kirill Morarenko committed be7947d

+ roster support

  • Participants
  • Parent commits 07a8e0f

Comments (0)

Files changed (13)

File include/channel.h

 
 	std::string& sticket() {return sticket_;}
 	const std::string& sticket() const {return sticket_;}
+
+	const FAvatarList& users() const {return users_;}
+	void addUser(const Avatar& avatar, const FAvatar& user);
+	void removeUser(const Avatar& avatar, const FAvatar& user);
+
+	typedef boost::function<void(const Avatar&, const Channel&)> UpdateCallback;
+	void setUpdateCallback(const UpdateCallback& cb) {update_cb_ = cb;}
     private:
 	bool private_;
 
 	FAvatarList users_;
 
 	std::string sticket_;
+
+	UpdateCallback update_cb_;
     };
     typedef std::map<int, Channel> ChannelList;
     
     typedef boost::function<void (Avatar&, ChannelList&, const std::string&)> ListChannelsCallback;
     void listChannels(Avatar& avatar, const ListChannelsCallback& cb);
 
-    typedef boost::function<void(Avatar&, Channel&, const std::string&)> JoinChannelCallback;
-    void joinChannel(Avatar& avatar, Channel& channel, const JoinChannelCallback& cb);
+    typedef boost::function<void(Avatar&, Channel&, const std::string&)> JoinChannelCallback;    
+    void joinChannel(Avatar& avatar, Channel& channel, const JoinChannelCallback& cb, const Channel::UpdateCallback& updateCb);
 }
 
 #endif

File include/favatar.h

 #define _CHATRU_FAVATAR_H_
 
 #include <string>
-#include <vector>
+#include <set>
 
-namespace chatru{
+namespace chatru{/*
     class FAvatar{
 	//represent external user
     public:
 	FAvatar(const std::string& name) : name_(name) {}
     private:
 	std::string name_;
-    };
-    typedef std::vector<FAvatar> FAvatarList;
+	};*/
+    typedef std::string FAvatar;
+    typedef std::set<FAvatar> FAvatarList;
 }
 #endif

File include/message.h

 #define _CHATRU_MESSAGE_H_
 
 #include <fwd.h>
+#include <channel_update.h>
 
 #include <boost/function.hpp>
 

File interfaces/build.sh

 #!/bin/sh
-swig -c++ -python crg.i
-gcc -fPIC -o _crg.so -shared -I /usr/include/python2.6/ -I ../include/ -L ../src -lcrg -lcurlpp -ljson -lpython2.6 ../interfaces/crg_wrap.cxx
+swig -c++ -python crg.i && gcc -fPIC -o _crg.so -shared -I /usr/include/python2.6/ -I ../include/ -L ../src -lcrg -lcurlpp -ljson -lpython2.6 ../interfaces/crg_wrap.cxx

File interfaces/crg.i

 %module crg
 %include "std_string.i"
 %include "std_map.i"
+%include "std_set.i"
 %{
 #include "avatar.h"
 #include "datalink.h"
      Py_XDECREF(result);
      Py_DECREF(function);
 }
+
+ void do_onChannelUpdate(PyObject* function, const chatru::Avatar& avatar, const chatru::Channel& cl){
+     PyObject *obj = SWIG_NewPointerObj(const_cast<chatru::Avatar*>(&avatar), SWIGTYPE_p_chatru__Avatar, 0);
+     PyObject *chl = SWIG_NewPointerObj(const_cast<chatru::Channel*>(&cl), SWIGTYPE_p_chatru__Channel, 0);
+     PyObject* arglist = Py_BuildValue("(OO)", obj, chl);
+     PyObject* result = PyEval_CallObject(function, arglist);
+     Py_DECREF(arglist);
+     Py_XDECREF(result);
+     //XXX memory leak
+     //Py_DECREF(function);
+}
+
  void do_onSent(PyObject* function, chatru::Avatar& avatar, const chatru::Channel& cl, const chatru::Message& mg, const std::string& error){
      PyObject *obj = SWIG_NewPointerObj(&avatar, SWIGTYPE_p_chatru__Avatar, 0);
      PyObject *chl = SWIG_NewPointerObj(const_cast<void*>((const void*)&cl), SWIGTYPE_p_chatru__Channel, 0);
 %}
 
 namespace chatru{
+    typedef std::string FAvatar;
+    typedef std::set<FAvatar> FAvatarList;
+}
+%template (FAvatarList) ::std::set<chatru::FAvatar>;
+
+
+namespace chatru{
     class DatalinkManager{
     public:
 	static void set_timeout(int t);
 
 	const int id() const;
 	const std::string& name() const;
-    };
+
+	FAvatarList users();
+    };    
 
     class Message{
     public:
 	Py_INCREF(onList);
 	chatru::listChannels(*avatar, boost::bind(&do_onList, onList, _1, _2, _3));
     }
-    void joinChannel(PyObject* avatar_ptr, PyObject* channel_ptr, PyObject* onJoin){
+    void joinChannel(PyObject* avatar_ptr, PyObject* channel_ptr, PyObject* onJoin, PyObject* onChannelUpdate){
 	chatru::Avatar* avatar;
 	if (SWIG_ConvertPtr(avatar_ptr, (void **) &avatar, SWIGTYPE_p_chatru__Avatar, SWIG_POINTER_EXCEPTION) == -1)
 	    return;
 	if (SWIG_ConvertPtr(channel_ptr, (void **) &channel, SWIGTYPE_p_chatru__Channel, SWIG_POINTER_EXCEPTION) == -1)
 	    return;
 	Py_INCREF(onJoin);
-	chatru::joinChannel(*avatar, *channel, boost::bind(&do_onJoin, onJoin, _1, _2, _3));
+	Py_INCREF(onChannelUpdate);
+	chatru::joinChannel(*avatar, *channel, boost::bind(&do_onJoin, onJoin,  _1, _2, _3), boost::bind(&do_onChannelUpdate, onChannelUpdate, _1, _2));
     }
     void sendMessage(PyObject* avatar_ptr, PyObject* channel_ptr, PyObject* message_ptr, PyObject* onSent){
 	chatru::Avatar* avatar;
 #include <formvalue.h>
 #include <stuff.h>
 
+#include "channel_impl.h"
+
 #include <boost/bind.hpp>
 #include <boost/lexical_cast.hpp>
 
 			Json::Reader reader;
 			//XXX
 			bool parsingSuccessful = reader.parse(root[i]["data"]["content"].asString(), content);
-			if (parsingSuccessful && content["type"] == "message"){
-			    //std::cout << Json::StyledWriter().write(content) << std::endl;
-			    Message msg(asInt(content["channel"]),
-					asLongLong(content["time"]),
-					content["msgtype"].asString(), 
-					content["sender"].asString(),
-					content["recipient"].asString(),
-					content["msgtext"].asString(),
-					root[i]["data"]["content"].asString()
-					);
-			    cb(avatar, msg, "");
+			if (parsingSuccessful){
+			    if (content["type"] == "message"){
+				//std::cout << Json::StyledWriter().write(content) << std::endl;
+				Message msg(asInt(content["channel"]),
+					    asLongLong(content["time"]),
+					    content["msgtype"].asString(), 
+					    content["sender"].asString(),
+					    content["recipient"].asString(),
+					    content["msgtext"].asString(),
+					    root[i]["data"]["content"].asString()
+					    );
+				cb(avatar, msg, "");
+			    } else if (content["type"] == "channelupdate"){
+				channelUpdate(avatar, content);
+			    } else if (content["type"] == "login"){
+				channelLogin(avatar, content);
+			    } else if (content["type"] == "logout"){
+				channelLogout(avatar, content);
+			    } else if (content["type"] == "userupdate"){
+				//channelLogout(avatar, content);
+			    } else{
+				std::cout << Json::StyledWriter().write(content) << std::endl;
+			    }
 			}
 		    }
 		}

File src/channel.cpp

 #include <ape.h>
 #include <stuff.h>
 
+#include "channel_impl.h"
+
 #include <boost/bind.hpp>
 #include <boost/lexical_cast.hpp>
 
     Channel::~Channel(){
     }
 
+    void Channel::addUser(const Avatar& avatar, const FAvatar& user){
+	users_.insert(user);
+	if (!!update_cb_){
+	    update_cb_(avatar, *this);
+	}
+    }
+
+    void Channel::removeUser(const Avatar& avatar, const FAvatar& user){
+	users_.erase(user);
+	if (!!update_cb_){
+	    update_cb_(avatar, *this);
+	}
+    }
+
     void listChannels(Avatar& avatar, const ListChannelsCallback& cb){
 	jsonGet(avatar, std::string("/channels?uticket=") + avatar.data()->uticket, boost::bind(&onListImpl, cb, _1, _2, _3));
 	//jsonGet(avatar.dlink(), std::string("/channels?uticket=") + avatar.data()->uticket, boost::bind(&onList, cb, _1, boost::ref(avatar), _2));
     }
 
     namespace{
-	void onJoin(JoinChannelCallback& cb, Channel& channel, const std::string& error, Avatar& avatar, Json::Value& value){
-	    if (error.empty())
+	void onJoin(JoinChannelCallback& cb, Channel& channel, Channel::UpdateCallback updateCb, const std::string& error, Avatar& avatar, Json::Value& value){
+	    if (error.empty()){
 		channel.sticket() = value.get("sticket", "").asString();
+		channel.setUpdateCallback(updateCb);
+	    }
 	    cb(avatar, channel, error);
 	}
     }
 
-    void joinChannel(Avatar& avatar, Channel& channel, const JoinChannelCallback& cb){
+    void joinChannel(Avatar& avatar, Channel& channel, const JoinChannelCallback& cb, const Channel::UpdateCallback& updateCb){
 	jsonGet(avatar, 
 		std::string("/chl?uticket=") + avatar.data()->uticket + "&channel=" + boost::lexical_cast<std::string>(channel.id()),
-		boost::bind(&onJoin, cb, boost::ref(avatar.data()->channels[channel.id()]), _1, _2, _3)
+		boost::bind(&onJoin, cb, boost::ref(avatar.data()->channels[channel.id()]), updateCb, _1, _2, _3)
 		);
     }
 
+    void channelUpdate(Avatar& avatar, const Json::Value& content){
+	//std::cout << Json::StyledWriter().write(content) << std::endl;
+	int channel_id = asInt(content["channel"]);
+	ChannelList::iterator it = avatar.data()->channels.find(channel_id);
+	if (it != avatar.data()->channels.end()){
+	    Channel& channel = it->second;
+	    // populate user list
+	    const Json::Value& users = content["users"];
+	    for (size_t i = 0; i < users.size(); ++i){
+		const Json::Value& user = users[i];
+		channel.addUser(avatar, user["username"].asString());
+	    }	    
+	}
+    }
+
+    void channelLogin(Avatar& avatar, const Json::Value& content){
+	int channel_id = asInt(content["channel"]);
+	ChannelList::iterator it = avatar.data()->channels.find(channel_id);
+	if (it != avatar.data()->channels.end()){
+	    Channel& channel = it->second;
+
+	    std::string user = content["username"].asString();
+	    channel.addUser(avatar, user);
+	}	
+    }
+
+    void channelLogout(Avatar& avatar, const Json::Value& content){
+	int channel_id = asInt(content["channel"]);
+	ChannelList::iterator it = avatar.data()->channels.find(channel_id);
+	if (it != avatar.data()->channels.end()){
+	    Channel& channel = it->second;
+
+	    std::string user = content["username"].asString();
+	    channel.removeUser(avatar, user);
+	}	
+    }
+
 }

File src/channel_impl.h

+//-*- mode: c++; c-indent-level: 4; c-basic-offset: 4; tab-width: 8 -*-
+#ifndef _CHATRU_CHANNEL_IMPL_H_
+#define _CHATRU_CHANNEL_IMPL_H_
+
+#include <fwd.h>
+
+namespace Json{
+    class Value;
+}
+
+namespace chatru{
+    void channelUpdate(Avatar& avatar, const Json::Value& content);
+    void channelLogin(Avatar& avatar, const Json::Value& content);
+    void channelLogout(Avatar& avatar, const Json::Value& content);
+}
+#endif

File tests/cleanup.cpp

 	chatru::DatalinkManager::shutdown();
 }
 
+void nopOnUpdate(const chatru::Avatar& avatar, const chatru::Channel& channel){
+}
+
 void onList(chatru::Avatar& avatar, chatru::ChannelList& channels, const std::string& error){
     std::cout << error << std::endl;
     if (error.empty()){
 	chatru::ChannelList::iterator it = channels.find(6);
 	if (it != channels.end()){
 	    chatru::Channel& chl = it->second;
-	    joinChannel(avatar, chl, &onJoin);
+	    joinChannel(avatar, chl, &onJoin, &nopOnUpdate);
 	}
 	else
 	    chatru::DatalinkManager::shutdown();	

File tests/commands/__init__.py

Empty file added.

File tests/commands/memo.py

+#coding=utf-8
+
+import crg
+
+def onSent2(*args, **kwargs):
+    pass
+
+def command(avatar, channel, msg, text, **kwargs):
+    parts = text.strip().split()
+    user = parts[1]
+    memo = " ".join(parts[2:])
+    crg.sendMessage(avatar, channel, crg.Message("not implemented yet", msg.sender() if channel.isPrivate() else ""), onSent2)
+#     stats = log.getStats(user)
+#     def genDelta(ts):
+#         if ts > 0:
+#             delta = datetime.datetime.fromtimestamp(round(time.time())) - datetime.datetime.fromtimestamp(ts / 100) #XXX
+#             return str(delta)
+#         else:
+#             return "неизвестно"
+#     answer = "Последнее сообщение: %s, покинул чат: %s" % (genDelta(stats["last_msg"]), genDelta(stats["left"]))
+#     crg.sendMessage(avatar, channel, crg.Message(answer, msg.sender() if channel.isPrivate() else ""), onSent)

File tests/ctest.cpp

 void nop(chatru::Avatar& avatar, const chatru::Channel& channel, const chatru::Message& message, const std::string& error){
 }
 
+void nopOnUpdate(const chatru::Avatar& avatar, const chatru::Channel& channel){
+}
+
 void onJoin(chatru::Avatar& avatar, const chatru::Channel& channel, const std::string& error){
     std::cout << "onJoin" << std::endl;
     if (error.empty()){
 	chatru::ChannelList::iterator it = channels.find(6);
 	if (it != channels.end()){
 	    chatru::Channel& chl = it->second;
-	    joinChannel(avatar, chl, &onJoin);
+	    joinChannel(avatar, chl, &onJoin, &nopOnUpdate);
 	}
 	else
 	    std::cerr << "no needed channel" << std::endl;

File tests/test.py

 
 def onList(avatar, channels, error):
     print "onList error: '%s' on avatar %s. Channels: %s" % (error, avatar.name(), ", ".join("%s (%d)" % (v.name(), k) for k, v in channels.items()))
-    for name in channels.values():
-        crg.joinChannel(avatar, name, onJoin)
+    for name in [n for n in channels.values() if n]:
+        crg.joinChannel(avatar, name, onJoin, onChannelUpdate)
 
     crg.receiveMessages(avatar, onMessage)
 
 def onJoin(avatar, channel, error):
-    print "on Join error: '%s', channel: %s, avatar: %s" % (error, channel.name(), avatar.name())
+    print "on Join error: '%s', channel: %s, avatar: %s, users: %s" % (error, channel.name(), avatar.name(), ", ".join(channel.users()))
     #crg.sendMessage(avatar, channel, crg.Message("тест"), onSent)
 
+def onChannelUpdate(avatar, channel):
+    try:
+        print "updated channel %s, users: %s" % (channel.name(), ", ".join(channel.users()))
+    except:
+        print traceback.format_exc()
+
 def onSent(avatar, channel, message, error):
     print "onSent error: '%s', message: %s, channel: %s, avatar: %s" % (error, message.text(), channel.name(), avatar.name())
 
     crg.sendMessage(avatar, channel, crg.Message("pong", msg.sender() if channel.isPrivate() else ""), onSent)
 
 def cmdHelp(avatar, channel, msg, **kwargs):
-    crg.sendMessage(avatar, channel, crg.Message("Команды: ping, mem, погода [город]", msg.sender() if channel.isPrivate() else ""), onSent)
+    crg.sendMessage(avatar, channel, crg.Message("Команды: ping, stat, погода [город], memo <user> <text>", msg.sender() if channel.isPrivate() else ""), onSent)
 
 def cmdWeather(avatar, channel, msg, text, **kwargs):
     def getWeather(city):
             result = chatify(u"\n" + unicode(traceback.format_exc(), "utf-8"))
         return (u"%s: %s" % (unicode(city, "utf-8"), result)).encode("utf-8")
         
-    parts = text.strip().split(' ')
+    parts = text.strip().split()
     if len(parts) == 1:
         city = "Москва"
     else:
     crg.sendMessage(avatar, channel, crg.Message(getWeather(city), msg.sender() if channel.isPrivate() else ""), onSent)
 
 def cmdThanks(avatar, channel, msg, text, **kwargs):
-    parts = text.strip().split(' ')
+    parts = text.strip().split()
     answer = "Пожалуйста!"
     if len(parts) > 1 and random.randint(0, 1) == 1:
         answer = "Пожалуйста, а вот это тебе: %s" % (" ".join(parts[1:]))
     crg.sendMessage(avatar, channel, crg.Message(answer, msg.sender() if channel.isPrivate() else ""), onSent)
 
 def cmdUserStat(avatar, channel, msg, text, **kwargs):
-    user = text.strip().split(' ')[-1].strip()
+    user = text.strip().split()[-1].strip()
     stats = log.getStats(user)
     def genDelta(ts):
         if ts > 0:
     answer = "Последнее сообщение: %s, покинул чат: %s" % (genDelta(stats["last_msg"]), genDelta(stats["left"]))
     crg.sendMessage(avatar, channel, crg.Message(answer, msg.sender() if channel.isPrivate() else ""), onSent)
 
-def cmdMem(avatar, channel, msg, **kwargs):
+def cmdStat(avatar, channel, msg, **kwargs):
     subprocess.Popen("date >> /tmp/memusage; ps ux | grep %d | grep -v grep >> /tmp/memusage; tail -n 2 /tmp/memusage > /tmp/lastmemusage" % os.getpid(), shell = True, stdout = subprocess.PIPE, stderr = subprocess.STDOUT).wait()
     with open("/tmp/lastmemusage") as f:
         answer = "\n".join(f.readlines())
     answer = s(chatify(u(answer)))
     crg.sendMessage(avatar, channel, crg.Message(answer, msg.sender() if channel.isPrivate() else ""), onSent)
 
+from commands.memo import command as cmdMemo
+
 def get_cmd(avatar, chl, msg):
     def nop(*args, **kwargs): pass
-    cmds = {"ping": cmdPing, "погода": cmdWeather, "спасибо": cmdThanks, "userstat": cmdUserStat, "mem": cmdMem}
+    cmds = {"ping": cmdPing, "погода": cmdWeather, "спасибо": cmdThanks, "userstat": cmdUserStat, "stat": cmdStat, "memo": cmdMemo}
 
     text = None
     if chl.isPrivate():
                 c(avatar = avatar, channel = chl, msg = msg, text = t)
         else:
             flag = "#"
-        print "%s [%d/%s] %s: %s" % (flag, msg.time(), chl.name(), msg.sender(), msg.text())
+
+        if chl.name() != "Викторина":
+            print "%s [%d/%s] %s: %s" % (flag, msg.time(), chl.name(), msg.sender(), msg.text())
 
 
     except: