Advertisement
Guest User

Untitled

a guest
May 15th, 2017
79
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 18.53 KB | None | 0 0
  1. #!/usr/bin/env python
  2.  
  3. import os
  4. import uuid
  5. import json
  6. import re
  7. import struct
  8. import socket
  9. import configparser
  10. import logging as log
  11. import MySQLdb
  12. import tornado.ioloop
  13. import tornado.web
  14. from tornado import websocket
  15. from tornado.util import bytes_type
  16. from tornado.iostream import StreamClosedError
  17.  
  18. ##########################################################################
  19. # Generate Configure Log System
  20. ##########################################################################
  21. logger = log.getLogger("WHNSMARCTI")
  22. logger.setLevel(log.INFO)
  23.  
  24. handler = log.FileHandler('whnlog.log')
  25. handler.setLevel(log.INFO)
  26.  
  27. formatter = log.Formatter(
  28. '%(asctime)s - %(name)s - %(levelname)s - %(message)s')
  29. handler.setFormatter(formatter)
  30. logger.addHandler(handler)
  31.  
  32. ##########################################################################
  33. # Generate configuration system (read file config.conf)
  34. ##########################################################################
  35. config = configparser.ConfigParser()
  36. config.read('config.conf')
  37.  
  38. localport = config.getint('Server', 'Port')
  39. debug = config.getboolean('Server', 'debug')
  40. atghost = config.get('Atg', 'host')
  41. atgport = config.getint('Atg', 'port')
  42. appInstance = config.get('Applications', 'name')
  43.  
  44. mysqlhost = config.get('db', 'host')
  45. mysqluser = config.get('db', 'user')
  46. mysqlpass = config.get('db', 'pass')
  47. mysqldbnm = config.get('db', 'dbnm')
  48.  
  49. conn = MySQLdb.connect(host=mysqlhost,
  50. unix_socket='/tmp/mysql.sock',
  51. user=mysqluser,
  52. passwd=mysqlpass,
  53. db=mysqldbnm
  54. )
  55. dbconn = conn.cursor()
  56. dbconn.execute("SELECT VERSION()")
  57. versiondb = dbconn.fetchone()
  58.  
  59. ##########################################################################
  60. # print info start app
  61. ##########################################################################
  62. if debug:
  63. print ("[INFO] >> load library sys... ")
  64. print ("[INFO] >> load library socket... ")
  65. print ("[INFO] >> load library time... ")
  66. print ("[INFO] >> load library traceback... ")
  67. print ("[INFO] >> load library treading ... ")
  68. print ("[INFO] >> load library multiprocessing... ")
  69. print ("[INFO] >> load library MySQLdb")
  70. print ("[INFO] >> load config local port %s " % (localport))
  71. print ("[INFO] >> load config ATG host %s " % (atghost))
  72. print ("[INFO] >> load config ATG port %s " % (atgport))
  73. print ("[INFO] >> load config AppName %s " % (appInstance))
  74.  
  75. print ("[INFO] >> connect to db %s" % (mysqldbnm))
  76. print ("[INFO] >> Database version %s" % (versiondb))
  77. # print
  78. # "-----------------------------------------------------------------------------"
  79.  
  80. logger.info('Loaded Module python')
  81. logger.info('Loaded configuration info from config.conf')
  82. logger.info('Connecting to db')
  83.  
  84. MAX_ROOMS = 100
  85. MAX_USERS_PER_ROOM = 100
  86. MAX_LEN_ROOMNAME = 20
  87. MAX_LEN_NICKNAME = 20
  88.  
  89. class RoomHandler(object):
  90. """Store data about connections, rooms, which users are in which rooms, etc."""
  91.  
  92. def __init__(self):
  93. self.client_info = {} # for each client id we'll store {'wsconn': wsconn, 'room':room, 'nick':nick}
  94. self.room_info = {} # dict to store a list of {'cid':cid, 'nick':nick , 'wsconn': wsconn} for each room
  95. self.pending_cwsconn = {} #pending client ws connection
  96. self.roomates = {} # store a set for each room, each contains the connections of the clients in the room.
  97.  
  98. def add_roomnick(self, room, nick):
  99. """Add nick to room. Return generated clientID"""
  100. # meant to be called from the main handler (page where somebody indicates a nickname and a room to join)
  101. if len(self.room_info) >= MAX_ROOMS:
  102. cid = -1
  103. else:
  104. if room in self.room_info and len(self.room_info[room]) >= MAX_USERS_PER_ROOM:
  105. cid = -2
  106. else:
  107. roomvalid= re.match(r'[\w-]+$', room)
  108. nickvalid= re.match(r'[\w-]+$', nick)
  109. if roomvalid == None :
  110. cid = -3
  111. else:
  112. if nickvalid == None :
  113. cid = -4
  114. else:
  115. cid = uuid.uuid4().hex # generate a client id.
  116. if not room in self.room_info: # it's a new room
  117. self.room_info[room] = []
  118. c = 1
  119. nn = nick
  120. nir = self.nicks_in_room(room)
  121. while True:
  122. if nn in nir:
  123. nn = nick + str(c)
  124. else:
  125. break
  126. c += 1
  127. self.add_pending(cid,room,nn)
  128.  
  129. return cid
  130.  
  131. def add_pending(self,cid,room,nick):
  132. logger.info("| ADD_PENDING | %s" % cid)
  133. self.pending_cwsconn[cid] = {'room': room, 'nick': nick} # we still don't know the WS connection for this client
  134.  
  135. def remove_pending(self,client_id):
  136. logger.info("| REMOVE_PENDING | %s" % client_id)
  137. if client_id in self.pending_cwsconn:
  138. del(self.pending_cwsconn[client_id]) #no longer pending
  139.  
  140. def add_client_wsconn(self, client_id, conn):
  141. """Store the websocket connection corresponding to an existing client."""
  142.  
  143. # add complete client info to the data structures, remove from the pending dict
  144. self.client_info[client_id] = self.pending_cwsconn[client_id]
  145. self.client_info[client_id]['wsconn'] = conn
  146. room = self.pending_cwsconn[client_id]['room']
  147. nick= self.pending_cwsconn[client_id]['nick']
  148. self.room_info[room].append({'cid': client_id, 'nick': nick, 'wsconn': conn})
  149. self.remove_pending(client_id)
  150. cid_room = self.client_info[client_id]['room']
  151. if cid_room in self.roomates:
  152. self.roomates[cid_room].add(conn)
  153. else:
  154. self.roomates[cid_room] = {conn}
  155.  
  156. for user in self.room_info[cid_room]:
  157. if user['cid'] == client_id:
  158. user['wsconn'] = conn
  159. break
  160.  
  161. # send "join" and and "nick_list" messages
  162. self.send_join_msg(client_id)
  163. nick_list = self.nicks_in_room(room)
  164. cwsconns = self.roomate_cwsconns(client_id)
  165. self.send_nicks_msg(cwsconns, nick_list)
  166.  
  167. def remove_client(self, client_id):
  168. """Remove all client information from the room handler."""
  169. cid_room = self.client_info[client_id]['room']
  170. nick = self.client_info[client_id]['nick']
  171. # first, remove the client connection from the corresponding room in self.roomates
  172. client_conn = self.client_info[client_id]['wsconn']
  173. if client_conn in self.roomates[cid_room]:
  174. self.roomates[cid_room].remove(client_conn)
  175. if len(self.roomates[cid_room]) == 0:
  176. del(self.roomates[cid_room])
  177. r_cwsconns = self.roomate_cwsconns(client_id)
  178. # filter out the list of connections r_cwsconns to remove clientID
  179. r_cwsconns = [conn for conn in r_cwsconns if conn != self.client_info[client_id]['wsconn']]
  180. self.client_info[client_id] = None
  181. for user in self.room_info[cid_room]:
  182. if user['cid'] == client_id:
  183. self.room_info[cid_room].remove(user)
  184. break
  185. self.send_leave_msg(nick, r_cwsconns)
  186. nick_list = self.nicks_in_room(cid_room)
  187. self.send_nicks_msg(r_cwsconns, nick_list)
  188. if len(self.room_info[cid_room]) == 0: # if room is empty, remove.
  189. del(self.room_info[cid_room])
  190. logger.info("| ROOM_REMOVED | %s" % cid_room)
  191.  
  192. def nicks_in_room(self, rn):
  193. """Return a list with the nicknames of the users currently connected to the specified room."""
  194. nir = [] # nicks in room
  195. for user in self.room_info[rn]:
  196. nir.append(user['nick'])
  197. return nir
  198.  
  199. def roomate_cwsconns(self, cid):
  200. """Return a list with the connections of the users currently connected to the room where
  201. the specified client (cid) is connected."""
  202. cid_room = self.client_info[cid]['room']
  203. r = []
  204. if cid_room in self.roomates:
  205. r = self.roomates[cid_room]
  206. return r
  207.  
  208.  
  209. def send_join_msg(self, client_id):
  210. """Send a message of type 'join' to all users connected to the room where client_id is connected."""
  211. nick = self.client_info[client_id]['nick']
  212. r_cwsconns = self.roomate_cwsconns(client_id)
  213. msg = {"msgtype": "join", "username": nick, "payload": " joined the chat room."}
  214. pmessage = json.dumps(msg)
  215. for conn in r_cwsconns:
  216. conn.write_message(pmessage)
  217.  
  218. @staticmethod
  219. def send_nicks_msg(conns, nick_list):
  220. """Send a message of type 'nick_list' (contains a list of nicknames) to all the specified connections."""
  221. msg = {"msgtype": "nick_list", "payload": nick_list}
  222. pmessage = json.dumps(msg)
  223. for c in conns:
  224. c.write_message(pmessage)
  225.  
  226. @staticmethod
  227. def send_leave_msg(nick, rconns):
  228. """Send a message of type 'leave', specifying the nickname that is leaving, to all the specified connections."""
  229. msg = {"msgtype": "leave", "username": nick, "payload": " left the chat room."}
  230. pmessage = json.dumps(msg)
  231. for conn in rconns:
  232. conn.write_message(pmessage)
  233.  
  234.  
  235. class MainHandler(tornado.web.RequestHandler):
  236.  
  237. def initialize(self, room_handler):
  238. """Store a reference to the "external" RoomHandler instance"""
  239. self.__rh = room_handler
  240.  
  241. def get(self, action = None):
  242. """Render chat.html if required arguments are present, render main.html otherwise."""
  243. if not action : # init startup sequence, won't be completed until the websocket connection is established.
  244. try:
  245. room = self.get_argument("room")
  246. nick = self.get_argument("nick")
  247. cid = self.__rh.add_roomnick(room, nick) # this alreay calls add_pending
  248. self.set_cookie("ftc_cid", cid)
  249. emsgs = ["The nickname provided was invalid. It can only contain letters, numbers, - and _.\nPlease try again.",
  250. "The room name provided was invalid. It can only contain letters, numbers, - and _.\nPlease try again.",
  251. "The maximum number of users in this room (%d) has been reached.\n\nPlease try again later." % MAX_USERS_PER_ROOM,
  252. "The maximum number of rooms (%d) has been reached.\n\nPlease try again later." % MAX_ROOMS]
  253. if cid == -1 or cid == -2:
  254. self.render("templates/maxreached.html",emsg=emsgs[cid])
  255. else:
  256. if cid < -2:
  257. self.render("templates/main.html",emsg = emsgs[cid])
  258. else:
  259. self.render("templates/cticlient.php", room_name=room)
  260. # self.render("templates/chat.html", room_name=room)
  261. except tornado.web.MissingArgumentError:
  262. self.render("templates/main.html",emsg = "")
  263. else:
  264. if action == "drop": # drop client from "pending" list. Client cannot establish WS connection.
  265. client_id = self.get_cookie("ftc_cid")
  266. if client_id:
  267. self.__rh.remove_pending(client_id)
  268. self.render("templates/nows.html")
  269.  
  270. websockets = []
  271. class ClientWSConnection(websocket.WebSocketHandler):
  272.  
  273. def initialize(self, room_handler):
  274. """Store a reference to the "external" RoomHandler instance"""
  275. self.__rh = room_handler
  276. self.atg = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  277. self.atg.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
  278. self.atg.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
  279. self.atg.connect((atghost, atgport))
  280.  
  281. def open(self):
  282. self.__clientID = self.get_cookie("ftc_cid")
  283. self.__rh.add_client_wsconn(self.__clientID, self)
  284. logger.info("| WS_OPENED | %s" % self.__clientID)
  285.  
  286. def on_message(self, message):
  287. msg = json.loads(message)
  288. mlen = len(msg['payload'])
  289. msg['username'] = self.__rh.client_info[self.__clientID]['nick']
  290.  
  291. # query data profile agent
  292. userid = msg['userid']
  293. try:
  294. conn = MySQLdb.connect(host=mysqlhost,
  295. user=mysqluser,
  296. passwd=mysqlpass,
  297. db=mysqldbnm)
  298. dbconn = conn.cursor()
  299.  
  300. dbconn.execute(
  301. "SELECT cti_agentpabx, cti_password, cti_extension, cti_afterstatus, cti_vdn FROM m_user WHERE muser_id = %s", ([userid]))
  302. for i in range(dbconn.rowcount):
  303. row = dbconn.fetchone()
  304. pabx_agent = row[0]
  305. pabx_pass = row[1]
  306. pabx_ext = row[2]
  307. pabx_afsta = row[3]
  308. pabx_vdn = row[4]
  309. except MySQLdb.Error:
  310. print ("Error %d: %s" % (e.args[0], e.args[1]))
  311. sys.exit(1)
  312. finally:
  313. dbconn.close()
  314.  
  315. varcommand = msg['payload']
  316.  
  317. if varcommand == 'login':
  318. msg_do_login = msg['userid'] + ';do_user_login;' + \
  319. pabx_agent + ';' + pabx_pass + ';' + pabx_ext + ';' + pabx_vdn
  320. self.atg.send(msg_do_login)
  321. call_do_login = self.atg.recv(1024)
  322. logger.info("[MSG-CTI] >> Login to device %s" % (msg_do_login))
  323. logger.info("[RES-CTI] >> %s" % (call_do_login))
  324.  
  325. msg_do_run_device = pabx_ext + ';do_run_device'
  326. self.atg.send(msg_do_run_device)
  327. call_do_run_device = self.atg.recv(1024)
  328. logger.info("[MSG-CTI] >> Run to device %s" % (msg_do_run_device))
  329. logger.info("[RES-CTI] >> %s" % (call_do_run_device))
  330.  
  331. # msg_acd_login = msg['userid'] + ';do_ag_login;' + pabx_ext + \
  332. # ';' + pabx_agent + ';' + pabx_pass + ';' + pabx_afsta
  333. # self.atg.send(msg_acd_login)
  334. # call_acd_login = self.atg.recv(1024)
  335. # logger.info("[MSG-CTI] >> ACD Login %s" % (msg_acd_login))
  336. # logger.info("[RES-CTI] >> %s" % (call_acd_login))
  337.  
  338. msg = {
  339. "msgtype": "login", "sec": msg['userid'], "agent_id": pabx_agent, "agent_ext": pabx_ext}
  340.  
  341. # query insert to agent activity
  342. #dbconn.execute("INSERT INTO Writers(Name) VALUES('Jack London')")
  343.  
  344. elif varcommand == 'ready':
  345. msg_acd_ready = msg['userid'] + ';do_ag_ready;' + \
  346. pabx_ext + ';' + pabx_agent + ';' + pabx_pass + ';0'
  347. self.atg.send(msg_acd_ready)
  348. call_acd_ready = self.atg.recv(1024)
  349. logger.info("[MSG-CTI] >> ACD Ready %s" % (msg_acd_ready))
  350. logger.info("[RES-CTI] >> %s" % (call_acd_ready))
  351.  
  352. msg = {
  353. "msgtype": "ready", "sec": msg['userid'], "agent_id": pabx_agent, "agent_ext": pabx_ext}
  354.  
  355. elif varcommand == 'notready':
  356. msg_acd_not_ready = msg['userid'] + ';do_ag_aux;' + \
  357. pabx_ext + ';' + pabx_agent + ';' + pabx_pass + ';0'
  358. self.atg.send(msg_acd_not_ready)
  359. call_acd_not_ready = self.atg.recv(1024)
  360. logger.info("[MSG-CTI] >> ACD Not Ready %s" % (msg_acd_not_ready))
  361. logger.info("[RES-CTI] >> %s" % (call_acd_not_ready))
  362.  
  363. msg = {"msgtype": "not ready",
  364. "sec": msg['userid'], "agent_id": pabx_agent, "agent_ext": pabx_ext}
  365.  
  366. elif varcommand == 'logout':
  367. msg_acd_shutdown = msg['userid'] + ';do_ag_logout;' + \
  368. pabx_ext + ';' + pabx_agent + ';' + pabx_pass + ';0'
  369. self.atg.send(msg_acd_shutdown)
  370. call_acd_shutdown = self.atg.recv(1024)
  371. logger.info("[MSG-CTI] >> ACD Not Ready %s" % (msg_acd_shutdown))
  372. logger.info("[RES-CTI] >> %s" % (call_acd_shutdown))
  373.  
  374. msg = {"msgtype": "shutdown",
  375. "sec": msg['userid'], "agent_id": pabx_agent, "agent_ext": pabx_ext}
  376.  
  377. elif varcommand == 'makecall':
  378. msg_do_make_call = msg['userid'] + ';do_dev_make_call;' + '202'
  379. self.atg.send(msg_do_make_call)
  380. call_do_make_call = self.atg.recv(1024)
  381. logger.info("[MSG-CTI] >> Make Call %s" % (msg_do_make_call))
  382. logger.info("[RES-CTI] >> %s" % (call_do_make_call))
  383.  
  384. msg = {"msgtype": "makecall",
  385. "sec": msg['userid'], "agent_id": pabx_agent, "agent_ext": pabx_ext}
  386.  
  387. else:
  388. msg = {"msgtype": "else"}
  389.  
  390. pmessage = json.dumps(msg)
  391. rconns = self.__rh.roomate_cwsconns(self.__clientID)
  392. logger.info("| MSG_RECEIVED | %s | %d" % (self.__clientID,mlen) )
  393. frame = self.make_frame(pmessage)
  394. for conn in rconns:
  395. #conn.write_message(pmessage)
  396. conn.write_frame(frame)
  397.  
  398. def make_frame(self, message):
  399. opcode = 0x1 # we know that binary is false, so opcode is s1
  400. message = tornado.escape.utf8(message)
  401. assert isinstance(message, bytes_type)
  402. finbit = 0x80
  403. mask_bit = 0
  404. frame = struct.pack("B", finbit | opcode)
  405. l = len(message)
  406. if l < 126:
  407. frame += struct.pack("B", l | mask_bit)
  408. elif l <= 0xFFFF:
  409. frame += struct.pack("!BH", 126 | mask_bit, l)
  410. else:
  411. frame += struct.pack("!BQ", 127 | mask_bit, l)
  412. frame += message
  413. return frame
  414.  
  415. def write_frame(self, frame):
  416. try:
  417. #self._write_frame(True, opcode, message)
  418. self.stream.write(frame)
  419. except StreamClosedError:
  420. pass
  421. #self._abort()
  422.  
  423. def on_close(self):
  424. cid = self.__clientID
  425. self.__rh.remove_client(self.__clientID)
  426. logger.info("| WS_CLOSED | %s" % cid)
  427.  
  428. def allow_draft76(self):
  429. return True
  430.  
  431. def receive(fd, events):
  432. """Receive a notify message from channel I listen."""
  433. for ws in websockets:
  434. ws.write_message("my message")
  435. tornado.ioloop.add_handler(connection.fileno(), receive, tornado.ioloop.WRITE)
  436.  
  437. if __name__ == "__main__":
  438. rh = RoomHandler()
  439. app = tornado.web.Application([
  440. (r"/(|drop)", MainHandler, {'room_handler': rh}),
  441. (r"/ws", ClientWSConnection, {'room_handler': rh})
  442. ],
  443. static_path=os.path.join(os.path.dirname(__file__), "static"),
  444. debug=debug
  445. )
  446. app.listen(localport)
  447. print ('[INFO] >> start %s.' % (appInstance))
  448. print ('[INFO] >> client listening on port %s ...' % (localport))
  449. tornado.ioloop.IOLoop.instance().start()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement