From 2ab755bb5c3cc8355e23cecc94a01e27e78ed966 Mon Sep 17 00:00:00 2001
From: electron128 <electron128@yahoo.com>
Date: Sun, 14 Feb 2016 14:57:41 +0100
Subject: [PATCH] libresapi: added chat/receive_status

usage:
$ curl http://<host:port>/localhost:8080/api/v2/chat/receive_status/<chat_id>
---
 libresapi/src/api/ChatHandler.cpp     | 195 +++++++++++++-------------
 libresapi/src/api/ChatHandler.h       |  22 ++-
 libresapi/src/api/GxsResponseTask.cpp |   1 +
 libresapi/src/api/GxsResponseTask.h   |   2 +-
 4 files changed, 121 insertions(+), 99 deletions(-)

diff --git a/libresapi/src/api/ChatHandler.cpp b/libresapi/src/api/ChatHandler.cpp
index 9d4062271..598db5c09 100644
--- a/libresapi/src/api/ChatHandler.cpp
+++ b/libresapi/src/api/ChatHandler.cpp
@@ -149,7 +149,7 @@ ChatHandler::ChatHandler(StateTokenServer *sts, RsNotify *notify, RsMsgs *msgs,
     addResourceHandler("send_message", this, &ChatHandler::handleSendMessage);
     addResourceHandler("mark_chat_as_read", this, &ChatHandler::handleMarkChatAsRead);
     addResourceHandler("info", this, &ChatHandler::handleInfo);
-    addResourceHandler("typing_label", this, &ChatHandler::handleTypingLabel);
+    addResourceHandler("receive_status", this, &ChatHandler::handleReceiveStatus);
     addResourceHandler("send_status", this, &ChatHandler::handleSendStatus);
     addResourceHandler("unread_msgs", this, &ChatHandler::handleUnreadMsgs);
 }
@@ -166,20 +166,21 @@ void ChatHandler::notifyChatMessage(const ChatMessage &msg)
     mRawMsgs.push_back(msg);
 }
 
-// to be removed
-/*
-ChatHandler::Lobby ChatHandler::getLobbyInfo(ChatLobbyId id)
+void ChatHandler::notifyChatStatus(const ChatId &chat_id, const std::string &status)
 {
-    tick();
-
-    RS_STACK_MUTEX(mMtx); // ********* LOCKED **********
-    for(std::vector<Lobby>::iterator vit = mLobbies.begin(); vit != mLobbies.end(); ++vit)
-        if(vit->id == id)
-            return *vit;
-    std::cerr << "ChatHandler::getLobbyInfo Error: Lobby not found" << std::endl;
-    return Lobby();
+    RS_STACK_MUTEX(mMtx); /********** LOCKED **********/
+    locked_storeTypingInfo(chat_id, status);
+}
+
+void ChatHandler::notifyChatLobbyEvent(uint64_t lobby_id, uint32_t event_type,
+                                       const RsGxsId &nickname, const std::string& any_string)
+{
+    RS_STACK_MUTEX(mMtx); /********** LOCKED **********/
+    if(event_type == RS_CHAT_LOBBY_EVENT_PEER_STATUS)
+    {
+        locked_storeTypingInfo(ChatId(lobby_id), any_string, nickname);
+    }
 }
-*/
 
 void ChatHandler::tick()
 {
@@ -510,6 +511,15 @@ void ChatHandler::getPlainText(const std::string& in, std::string &out, std::vec
     }
 }
 
+void ChatHandler::locked_storeTypingInfo(const ChatId &chat_id, std::string status, RsGxsId lobby_gxs_id)
+{
+    TypingLabelInfo& info = mTypingLabelInfo[chat_id];
+    info.timestamp = time(0);
+    info.status = status;
+    mStateTokenServer->replaceToken(info.state_token);
+    info.author_id = lobby_gxs_id;
+}
+
 void ChatHandler::handleWildcard(Request &/*req*/, Response &resp)
 {
     RS_STACK_MUTEX(mMtx); /********** LOCKED **********/
@@ -664,89 +674,6 @@ void ChatHandler::handleMarkChatAsRead(Request &req, Response &resp)
     mStateTokenServer->replaceToken(mUnreadMsgsStateToken);
 }
 
-// to be removed
-// we do now cache chat info, to be able to include it in new message notify easily
-/*
-class InfoResponseTask: public GxsResponseTask
-{
-public:
-    InfoResponseTask(ChatHandler* ch, RsPeers* peers, RsIdentity* identity): GxsResponseTask(identity, 0), mChatHandler(ch), mRsPeers(peers), mState(BEGIN){}
-
-    enum State {BEGIN, WAITING};
-    ChatHandler* mChatHandler;
-    RsPeers* mRsPeers;
-    State mState;
-    bool is_broadcast;
-    bool is_gxs_id;
-    bool is_lobby;
-    bool is_peer;
-    std::string remote_author_id;
-    std::string remote_author_name;
-    virtual void gxsDoWork(Request& req, Response& resp)
-    {
-        ChatId id(req.mPath.top());
-        if(id.isNotSet())
-        {
-            resp.setFail("not a valid chat id");
-            done();
-            return;
-        }
-        if(mState == BEGIN)
-        {
-            is_broadcast = false;
-            is_gxs_id = false;
-            is_lobby = false;
-            is_peer = false;
-            if(id.isBroadcast())
-            {
-                is_broadcast = true;
-            }
-            else if(id.isGxsId())
-            {
-                is_gxs_id = true;
-                remote_author_id = id.toGxsId().toStdString();
-                requestGxsId(id.toGxsId());
-            }
-            else if(id.isLobbyId())
-            {
-                is_lobby = true;
-                remote_author_id = "";
-                remote_author_name = mChatHandler->getLobbyInfo(id.toLobbyId()).name;
-            }
-            else if(id.isPeerId())
-            {
-                is_peer = true;
-                remote_author_id = id.toPeerId().toStdString();
-                remote_author_name = mRsPeers->getPeerName(id.toPeerId());
-            }
-            else
-            {
-                std::cerr << "Error in InfoResponseTask::gxsDoWork(): unhandled chat_id=" << id.toStdString() << std::endl;
-            }
-            mState = WAITING;
-        }
-        else
-        {
-            if(is_gxs_id)
-                remote_author_name = getName(id.toGxsId());
-            resp.mDataStream << makeKeyValueReference("remote_author_id", remote_author_id)
-                             << makeKeyValueReference("remote_author_name", remote_author_name)
-                             << makeKeyValueReference("is_broadcast", is_broadcast)
-                             << makeKeyValueReference("is_gxs_id", is_gxs_id)
-                             << makeKeyValueReference("is_lobby", is_lobby)
-                             << makeKeyValueReference("is_peer", is_peer);
-            resp.setOk();
-            done();
-        }
-    }
-};
-
-ResponseTask *ChatHandler::handleInfo(Request &req, Response &resp)
-{
-    return new InfoResponseTask(this, mRsPeers, mRsIdentity);
-}
-*/
-
 void ChatHandler::handleInfo(Request &req, Response &resp)
 {
     RS_STACK_MUTEX(mMtx); /********** LOCKED **********/
@@ -766,9 +693,83 @@ void ChatHandler::handleInfo(Request &req, Response &resp)
     resp.setOk();
 }
 
-void ChatHandler::handleTypingLabel(Request &/*req*/, Response &/*resp*/)
+class SendTypingLabelInfo: public GxsResponseTask
 {
+public:
+    SendTypingLabelInfo(RsIdentity* identity, RsPeers* peers, ChatId id, const ChatHandler::TypingLabelInfo& info):
+        GxsResponseTask(identity), mState(BEGIN), mPeers(peers),mId(id), mInfo(info) {}
+private:
+    enum State {BEGIN, WAITING_ID};
+    State mState;
+    RsPeers* mPeers;
+    ChatId mId;
+    ChatHandler::TypingLabelInfo mInfo;
+protected:
+    void gxsDoWork(Request& /*req*/, Response& resp)
+    {
+        if(mState == BEGIN)
+        {
+            // lobby and distant require to fetch a gxs_id
+            if(mId.isLobbyId())
+            {
+                requestGxsId(mInfo.author_id);
+            }
+            else if(mId.isDistantChatId())
+            {
+                DistantChatPeerInfo dcpinfo ;
+                rsMsgs->getDistantChatStatus(mId.toDistantChatId(), dcpinfo);
+                requestGxsId(dcpinfo.to_id);
+            }
+            mState = WAITING_ID;
+        }
+        else
+        {
+            std::string name = "BUG: case not handled in SendTypingLabelInfo";
+            if(mId.isPeerId())
+            {
+                name = mPeers->getPeerName(mId.toPeerId());
+            }
+            else if(mId.isDistantChatId())
+            {
+                DistantChatPeerInfo dcpinfo ;
+                rsMsgs->getDistantChatStatus(mId.toDistantChatId(), dcpinfo);
+                name = getName(dcpinfo.to_id);
+            }
+            else if(mId.isLobbyId())
+            {
+                name = getName(mInfo.author_id);
+            }
+            else if(mId.isBroadcast())
+            {
+                name = mPeers->getPeerName(mId.broadcast_status_peer_id);
+            }
+            uint32_t ts = mInfo.timestamp;
+            resp.mDataStream << makeKeyValueReference("author_name", name)
+                             << makeKeyValueReference("timestamp", ts)
+                             << makeKeyValueReference("status_string", mInfo.status);
+            resp.mStateToken = mInfo.state_token;
+            resp.setOk();
+            done();
+        }
+    }
+};
 
+ResponseTask* ChatHandler::handleReceiveStatus(Request &req, Response &resp)
+{
+    RS_STACK_MUTEX(mMtx); /********** LOCKED **********/
+    ChatId id(req.mPath.top());
+    if(id.isNotSet())
+    {
+        resp.setFail("\""+req.mPath.top()+"\" is not a valid chat id");
+        return 0;
+    }
+    std::map<ChatId, TypingLabelInfo>::iterator mit = mTypingLabelInfo.find(id);
+    if(mit == mTypingLabelInfo.end())
+    {
+        locked_storeTypingInfo(id, "");
+        mit = mTypingLabelInfo.find(id);
+    }
+    return new SendTypingLabelInfo(mRsIdentity, mRsPeers, id, mit->second);
 }
 
 void ChatHandler::handleSendStatus(Request &req, Response &resp)
diff --git a/libresapi/src/api/ChatHandler.h b/libresapi/src/api/ChatHandler.h
index 931e10514..e16aa445b 100644
--- a/libresapi/src/api/ChatHandler.h
+++ b/libresapi/src/api/ChatHandler.h
@@ -26,6 +26,13 @@ public:
     // note: this may get called from the own and from foreign threads
     virtual void notifyChatMessage(const ChatMessage& msg);
 
+    // typing label for peer, broadcast and distant chat
+    virtual void notifyChatStatus     (const ChatId&      /* chat_id  */, const std::string& /* status_string */);
+
+    //typing label for lobby chat, peer join and leave messages
+    virtual void notifyChatLobbyEvent (uint64_t           /* lobby id */, uint32_t           /* event type    */ ,
+                                       const RsGxsId& /* nickname */,const std::string& /* any string */);
+
     // from tickable
     virtual void tick();
 
@@ -97,6 +104,15 @@ public:
         std::string remote_author_name;
     };
 
+    class TypingLabelInfo{
+    public:
+        time_t timestamp;
+        std::string status;
+        StateToken state_token;
+        // only for lobbies
+        RsGxsId author_id;
+    };
+
 private:
     void handleWildcard(Request& req, Response& resp);
     void handleLobbies(Request& req, Response& resp);
@@ -107,11 +123,13 @@ private:
     void handleSendMessage(Request& req, Response& resp);
     void handleMarkChatAsRead(Request& req, Response& resp);
     void handleInfo(Request& req, Response& resp);
-    void handleTypingLabel(Request& req, Response& resp);
+    ResponseTask *handleReceiveStatus(Request& req, Response& resp);
     void handleSendStatus(Request& req, Response& resp);
     void handleUnreadMsgs(Request& req, Response& resp);
 
     void getPlainText(const std::string& in, std::string &out, std::vector<Triple> &links);
+    // last parameter is only used for lobbies!
+    void locked_storeTypingInfo(const ChatId& chat_id, std::string status, RsGxsId lobby_gxs_id = RsGxsId());
 
     StateTokenServer* mStateTokenServer;
     RsNotify* mNotify;
@@ -128,6 +146,8 @@ private:
 
     std::map<ChatId, ChatInfo> mChatInfo;
 
+    std::map<ChatId, TypingLabelInfo> mTypingLabelInfo;
+
     StateToken mLobbiesStateToken;
     std::vector<Lobby> mLobbies;
 
diff --git a/libresapi/src/api/GxsResponseTask.cpp b/libresapi/src/api/GxsResponseTask.cpp
index 581f38ba6..dcb4ebb51 100644
--- a/libresapi/src/api/GxsResponseTask.cpp
+++ b/libresapi/src/api/GxsResponseTask.cpp
@@ -62,6 +62,7 @@ bool GxsResponseTask::doWork(Request &req, Response &resp)
         {
             more = false; // pause when an id failed, to give the service time tim fetch the data
             ready = false;
+            // TODO: remove identities which failed many times from list, to avoid blocking when ids fail
         }
     }
     if(!ready)
diff --git a/libresapi/src/api/GxsResponseTask.h b/libresapi/src/api/GxsResponseTask.h
index 7e133cdb9..8200b3eee 100644
--- a/libresapi/src/api/GxsResponseTask.h
+++ b/libresapi/src/api/GxsResponseTask.h
@@ -15,7 +15,7 @@ class GxsResponseTask: public ResponseTask
 {
 public:
     // token service is allowed to be null if no token functions are wanted
-    GxsResponseTask(RsIdentity* id_service, RsTokenService* token_service);
+    GxsResponseTask(RsIdentity* id_service, RsTokenService* token_service = 0);
     virtual bool doWork(Request &req, Response& resp);
 
 protected: