otsdaq-utilities  3.05.00
ChatSupervisor.cc
1 #include "otsdaq-utilities/Chat/ChatSupervisor.h"
2 #include "otsdaq/CgiDataUtilities/CgiDataUtilities.h"
3 #include "otsdaq/Macros/CoutMacros.h"
4 #include "otsdaq/MessageFacility/MessageFacility.h"
5 #include "otsdaq/XmlUtilities/HttpXmlDocument.h"
6 
7 #include <xdaq/NamespaceURI.h>
8 
9 #include <iostream>
10 
11 using namespace ots;
12 
13 #undef __MF_SUBJECT__
14 #define __MF_SUBJECT__ "Chat"
15 
16 XDAQ_INSTANTIATOR_IMPL(ChatSupervisor)
17 
18 //==============================================================================
19 ChatSupervisor::ChatSupervisor(xdaq::ApplicationStub* stub) : CoreSupervisorBase(stub)
20 {
21  INIT_MF("." /*directory used is USER_DATA/LOG/.*/);
22 
23  ChatLastUpdateIndex = 1; // skip 0
24 
25  // run ots_setup_slack.sh to enable OTS_EN_SLACK environment variable
26  enableSlackChat = (std::getenv("OTS_EN_SLACK") != nullptr &&
27  std::string(std::getenv("OTS_EN_SLACK")) == "1");
28  if(enableSlackChat)
29  {
30  const char* env = std::getenv("OTSDAQ_UTILITIES_DIR");
31  chatSupervisorToolsPath_ = env ? env : "";
32 
33  if(chatSupervisorToolsPath_.empty())
34  enableSlackChat = false;
35  if(chatSupervisorToolsPath_.back() != '/')
36  chatSupervisorToolsPath_ += '/';
37  chatSupervisorToolsPath_ += "tools/";
38 
39  __COUT__ << "ChatSupervisor: Slack chat "
40  << (enableSlackChat ? "enabled" : "disabled") << __E__;
41  __COUT__ << "ChatSupervisor path: " << chatSupervisorToolsPath_ << __E__;
42  }
43 }
44 
45 //==============================================================================
46 ChatSupervisor::~ChatSupervisor(void) { destroy(); }
47 
48 //==============================================================================
49 void ChatSupervisor::destroy(void)
50 {
51  // called by destructor
52 }
53 
54 //==============================================================================
55 void ChatSupervisor::defaultPage(xgi::Input* /* cgiIn */, xgi::Output* out)
56 {
57  out->getHTTPResponseHeader().addHeader("Access-Control-Allow-Origin", "*");
58  out->getHTTPResponseHeader().addHeader("Pragma", "no-cache");
59 
60  *out << "<!DOCTYPE HTML><html lang='en'><frameset col='100%' row='100%'><frame "
61  "src='/WebPath/html/Chat.html?urn="
62  << this->getApplicationDescriptor()->getLocalId() << "'></frameset></html>";
63 } //end defaultPage()
64 
65 //==============================================================================
69 {
70  CorePropertySupervisorBase::setSupervisorProperty(
71  CorePropertySupervisorBase::SUPERVISOR_PROPERTIES.AutomatedRequestTypes,
72  "RefreshChat");
73 }
74 
75 //==============================================================================
79 void ChatSupervisor::request(const std::string& requestType,
80  cgicc::Cgicc& cgiIn,
81  HttpXmlDocument& xmlOut,
82  const WebUsers::RequestUserInfo& /*userInfo*/)
83 {
84  __COUTVS__(40, requestType);
85 
86  // Commands:
87  // RefreshChat
88  // RefreshUsers
89  // SendChat
90 
91  cleanupExpiredChats();
92 
93  if(requestType == "RefreshChat")
94  {
95  std::string lastUpdateIndexString =
96  CgiDataUtilities::postData(cgiIn, "lastUpdateIndex");
97  std::string user = CgiDataUtilities::postData(cgiIn, "user");
98  uint64_t lastUpdateIndex;
99  sscanf(lastUpdateIndexString.c_str(), "%lu", &lastUpdateIndex);
100 
101  insertChatRefresh(&xmlOut, lastUpdateIndex, user);
102  }
103  else if(requestType == "RefreshUsers")
104  {
105  insertActiveUsers(&xmlOut);
106  }
107  else if(requestType == "SendChat")
108  {
109  std::string chat = CgiDataUtilities::postData(cgiIn, "chat");
110  std::string user = CgiDataUtilities::postData(cgiIn, "user");
111 
112  escapeChat(chat);
113 
114  newChat(chat, user);
115  }
116  else if(requestType == "PageUser")
117  {
118  std::string topage = CgiDataUtilities::postData(cgiIn, "topage");
119  unsigned int topageId = CgiDataUtilities::postDataAsInt(cgiIn, "topageId");
120  std::string user = CgiDataUtilities::postData(cgiIn, "user");
121 
122  __COUT__ << "Paging = " << topage.substr(0, 10)
123  << "... from user = " << user.substr(0, 10) << std::endl;
124 
125  __COUTV__(topageId);
126 
127  theRemoteWebUsers_.sendSystemMessage(topage,
128  user + " is paging you to come chat.");
129  }
130  else
131  {
132  __SUP_SS__ << "requestType Request, " << requestType
133  << ", not recognized by the Chat Editor Supervisor (was it intended "
134  "for another Supervisor?)."
135  << __E__;
136  __SUP_SS_THROW__;
137  }
138 
139 } // end request()
140 
141 //==============================================================================
145 void ChatSupervisor::escapeChat(std::string& /*chat*/)
146 {
147  // char reserved[] = {'"','\'','&','<','>'};
148  // std::string replace[] = {"&#34;","&#39;","&#38;","&#60;","&#62;"};
149  // for(uint64_t i=0;i<chat.size();++i)
150  // for(uint64_t j=0;j<chat.size();++j)
151  // if(chat[i] ==
152 } // end escapeChat()
153 
154 //==============================================================================
156 void ChatSupervisor::insertActiveUsers(HttpXmlDocument* xmlOut)
157 {
158  xmlOut->addTextElementToData("active_users", theRemoteWebUsers_.getActiveUserList());
159 } // end insertActiveUsers()
160 
161 //==============================================================================
168 void ChatSupervisor::insertChatRefresh(HttpXmlDocument* xmlOut,
169  uint64_t lastUpdateIndex,
170  const std::string& user)
171 {
172  newUser(user);
173 
174  if(!isLastUpdateIndexStale(lastUpdateIndex))
175  return; // if lastUpdateIndex is current, return nothing
176 
177  // return new update index, full chat user list, and new chats!
178 
179  char tempStr[50];
180  sprintf(tempStr, "%lu", ChatLastUpdateIndex);
181  xmlOut->addTextElementToData("last_update_index", tempStr);
182 
183  // get all users
184  xmlOut->addTextElementToData("chat_users", "");
185  for(uint64_t i = 0; i < ChatUsers_.size(); ++i)
186  xmlOut->addTextElementToParent("chat_user", ChatUsers_[i], "chat_users");
187 
188  //if lastUpdateIndex == 0, first request, so give give full history!
189 
190  // get all accounts
191  xmlOut->addTextElementToData("chat_history", "");
192  for(uint64_t i = 0; i < ChatHistoryEntry_.size(); ++i) // output oldest to new
193  {
194  __COUTT__ << "Chat[" << i << "]: " << ChatHistoryIndex_[i] << " vs "
195  << lastUpdateIndex << __E__;
196  if(isChatOld(ChatHistoryIndex_[i], lastUpdateIndex))
197  continue;
198 
199  xmlOut->addTextElementToParent(
200  "chat_entry", ChatHistoryEntry_[i], "chat_history");
201  xmlOut->addTextElementToParent(
202  "chat_author", ChatHistoryAuthor_[i], "chat_history");
203  sprintf(tempStr, "%lu", ChatHistoryTime_[i]);
204  xmlOut->addTextElementToParent("chat_time", tempStr, "chat_history");
205  }
206 } // end insertChatRefresh()
207 
208 //==============================================================================
211 void ChatSupervisor::newUser(const std::string& user)
212 {
213  for(uint64_t i = 0; i < ChatUsers_.size(); ++i)
214  if(ChatUsers_[i] == user)
215  {
216  ChatUsersTime_[i] = time(0); // update time
217  return; // do not add new if found
218  }
219 
220  __COUT__ << "New user: " << user << std::endl;
221  // add and increment
222  ChatUsers_.push_back(user);
223  ChatUsersTime_.push_back(time(0));
224  newChat(user + " joined the chat.",
225  "ots"); // add status message to chat, increment update
226 } // end newUser()
227 
228 //==============================================================================
231 void ChatSupervisor::newChat(const std::string& chat, const std::string& user)
232 {
233  ChatHistoryEntry_.push_back(chat);
234  ChatHistoryAuthor_.push_back(user);
235  ChatHistoryTime_.push_back(time(0));
236  ChatHistoryIndex_.push_back(incrementAndGetLastUpdate());
237  if(enableSlackChat)
238  sendToSlack(user, chat);
239 }
240 
241 //==============================================================================
244 bool ChatSupervisor::isChatOld(uint64_t chatIndex, uint64_t last)
245 {
246  return (last - chatIndex < (uint64_t(1) << 62));
247 }
248 
249 //==============================================================================
251 bool ChatSupervisor::isLastUpdateIndexStale(uint64_t last)
252 {
253  return ChatLastUpdateIndex != last;
254 }
255 
256 //==============================================================================
258 uint64_t ChatSupervisor::incrementAndGetLastUpdate()
259 {
260  if(!++ChatLastUpdateIndex)
261  ++ChatLastUpdateIndex; // skip 0
262  return ChatLastUpdateIndex;
263 }
264 
265 //==============================================================================
268 void ChatSupervisor::cleanupExpiredChats()
269 {
270  for(uint64_t i = 0; i < ChatHistoryEntry_.size(); ++i)
271  if(i >= CHAT_HISTORY_MAX_ENTRIES ||
272  ChatHistoryTime_[i] + CHAT_HISTORY_EXPIRATION_TIME < time(0)) // expired
273  {
274  removeChatHistoryEntry(i);
275  --i; // rewind loop
276  }
277  else
278  break; // chronological order, so first encountered that is still valid exit
279  // loop
280 
281  for(uint64_t i = 0; i < ChatUsers_.size(); ++i)
282  if(ChatUsersTime_[i] + CHAT_HISTORY_EXPIRATION_TIME < time(0)) // expired
283  {
284  removeChatUserEntry(i);
285  --i; // rewind loop
286  }
287  else
288  break; // chronological order, so first encountered that is still valid exit
289  // loop
290 }
291 
292 //==============================================================================
294 void ChatSupervisor::removeChatHistoryEntry(uint64_t i)
295 {
296  ChatHistoryEntry_.erase(ChatHistoryEntry_.begin() + i);
297  ChatHistoryTime_.erase(ChatHistoryTime_.begin() + i);
298  ChatHistoryAuthor_.erase(ChatHistoryAuthor_.begin() + i);
299  ChatHistoryIndex_.erase(ChatHistoryIndex_.begin() + i);
300 }
301 
302 //==============================================================================
304 void ChatSupervisor::removeChatUserEntry(uint64_t i)
305 {
306  newChat(ChatUsers_[i] + " left the chat.",
307  "ots"); // add status message to chat, increment update
308  ChatUsers_.erase(ChatUsers_.begin() + i);
309  ChatUsersTime_.erase(ChatUsersTime_.begin() + i);
310 }
311 
312 //==============================================================================
314 void ChatSupervisor::sendToSlack(const std::string& user, const std::string& message)
315 {
316  std::string command = "python3 " + chatSupervisorToolsPath_ + "SendSlackChat.py " +
317  "--message \"" + message + "\" --user " + user;
318  __COUT__ << "Executing command: " << command << __E__;
319 
320  try
321  {
322  auto result = StringMacros::exec(command.c_str());
323 
324  if(!result.empty() && result.find("Error:") != std::string::npos)
325  __COUT__ << "Error from SendSlackChat.py: " << result << __E__;
326  else if(!result.empty())
327  __COUT__ << "Response from SendSlackChat.py: " << result << __E__;
328  }
329  catch(const std::exception& e)
330  {
331  __COUT__ << "Exception while executing command: " << e.what() << __E__;
332  }
333 }
static std::string postData(cgicc::Cgicc &cgi, const std::string &needle)
virtual void request(const std::string &requestType, cgicc::Cgicc &cgiIn, HttpXmlDocument &xmlOut, const WebUsers::RequestUserInfo &userInfo) override
end forceSupervisorPropertyValues()
virtual void forceSupervisorPropertyValues(void) override
override to force supervisor property values (and ignore user settings)
xercesc::DOMElement * addTextElementToParent(const std::string &childName, const std::string &childText, xercesc::DOMElement *parent)
void INIT_MF(const char *name)
static std::string exec(const char *cmd)