Example #1
0
def get_toredis_client():
    redis_client = Client(host=settings.redis_host,
                          port=settings.redis_port,
                          selected_db=settings.redis_db,
                          password=settings.redis_password)
    redis_client.connect()
    return redis_client
Example #2
0
    def __init__(self, *args, **kwargs):
        super(StatsWSHandler, self).__init__(*args, **kwargs)

        self.client = Client(self.application.options.redis_host,
                             self.application.options.redis_port,
                             selected_db=self.application.options.redis_db)

        self.listen()
Example #3
0
def async_client(host=None, port=None, db=None, io_loop=None):
    """
    Returns a new redis client instance.
    """
    options = _get_options(host, port, db)
    c = Client(**options)
    return c
Example #4
0
	def __init__(self, *args, **kwargs):
		self.closed_channels = None
		self.message_creator = WebRtcMessageCreator(None, None)
		super(MessagesHandler, self).__init__()
		self.webrtc_ids = {}
		self.id = None  # child init
		self.last_client_ping = 0
		self.user_id = 0  # anonymous by default
		self.ip = None
		from chat import global_redis
		self.async_redis_publisher = global_redis.async_redis_publisher
		self.sync_redis = global_redis.sync_redis
		self.channels = []
		self._logger = None
		self.async_redis = Client(host=REDIS_HOST, port=REDIS_PORT, selected_db=REDIS_DB)
		self.patch_tornadoredis()
		# input websocket messages handlers
		# The handler is determined by @VarNames.EVENT
		self.process_ws_message = {
			Actions.GET_MESSAGES: self.process_get_messages,
			Actions.GET_MESSAGES_BY_IDS: self.process_get_messages_by_ids,
			Actions.PRINT_MESSAGE: self.process_send_message,
			Actions.DELETE_ROOM: self.delete_room,
			Actions.EDIT_MESSAGE: self.edit_message,
			Actions.CREATE_ROOM: self.create_new_room,
			Actions.CREATE_CHANNEL: self.create_new_channel,
			Actions.SAVE_CHANNEL_SETTINGS: self.save_channels_settings,
			Actions.SAVE_ROOM_SETTINGS: self.save_room_settings,
			Actions.DELETE_CHANNEL: self.delete_channel,
			Actions.SET_USER_PROFILE: self.profile_save_user,
			Actions.SET_SETTINGS: self.profile_save_settings,
			Actions.INVITE_USER: self.invite_user,
			Actions.PING: self.respond_ping,
			Actions.PONG: self.process_pong_message,
			Actions.SEARCH_MESSAGES: self.search_messages,
			Actions.SYNC_HISTORY: self.sync_history,
			Actions.SHOW_I_TYPE: self.show_i_type,
		}
		# Handlers for redis messages, if handler returns true - message won't be sent to client
		# The handler is determined by @VarNames.EVENT
		self.process_pubsub_message = {
			Actions.CREATE_ROOM: self.send_client_new_channel,
			Actions.DELETE_ROOM: self.send_client_delete_channel,
			Actions.INVITE_USER: self.send_client_new_channel,
			Actions.ADD_INVITE: self.send_client_new_channel,
			Actions.PING: self.process_ping_message,
		}
Example #5
0
 def redis_listen(self):
     self.redis_client = Client(
         host=os.environ["REDIS_HOST"],
         port=int(os.environ["REDIS_PORT"]),
         selected_db=int(os.environ["REDIS_DB"]),
     )
     yield Task(self.redis_client.subscribe, self.channels.values())
     self.redis_client.listen(self.on_redis_message, self.on_redis_unsubscribe)
Example #6
0
 def __init__(self, **kwargs):
     routes = [
         (r'/socket', SprintHandler),
         (r'/(?P<model>task|sprint|user)/(?P<pk>[0-9]+)', UpdateHandler),
     ]
     super().__init__(routes, **kwargs)
     self.subscriber = RedisSubscriber(Client())
     self.publisher = Redis()
     self._key = config('WATERCOOLER_SECRET_KEY')
     self.signer = signing.TimestampSigner(self._key)
Example #7
0
 def __init__(self, **kwargs):
     routes = [
         (r'/(?P<sprint>[0-9]+)', SprintHandler),
         (r'/(?P<model>task|sprint|user)/(?<pk>[0-9]+)', UpdateHandler)
     ]
     super().__init__(routes, **kwargs)
     self.subscriber = RedisSubscriber(Client())
     self.publisher = Redis()
     self._key = os.environ.get('WATERCOOLER_SECRET', 'pTyz1dzMeVUGrb0Su4QXsP984qTlvQRHpFnnlHuHs')
     self.signer = TimestampSigner(self._key)
Example #8
0
class StatsWSHandler(WebSocketHandler):
    def __init__(self, *args, **kwargs):
        super(StatsWSHandler, self).__init__(*args, **kwargs)

        self.client = Client(
            self.application.options.redis_host,
            self.application.options.redis_port,
            selected_db=self.application.options.redis_db
        )

        self.listen()

    @coroutine
    def listen(self):
        client = self.client

        client.connect()

        yield Task(
            client.subscribe,
            'stats'
        )

        self.client.listen(self.on_message)

    def on_message(self, msg):
        if msg.kind == 'message':
            self.write_message(str(msg.body))

        if msg.kind == 'disconnect':
            self.close()

    def on_close(self):
        client = self.client
        if client.subscribed:
            client.unsubscribe('stats')
            client.disconnect()

    def check_origin(self, origin):
        if self.application.debug:
            return True

        return super(StatsWSHandler, self).check_origin(origin)
Example #9
0
    def __init__(self, *args, **kwargs):
        super(StatsWSHandler, self).__init__(*args, **kwargs)

        self.client = Client(
            self.application.options.redis_host,
            self.application.options.redis_port,
            selected_db=self.application.options.redis_db
        )

        self.listen()
Example #10
0
class StatsWSHandler(WebSocketHandler):
    def __init__(self, *args, **kwargs):
        super(StatsWSHandler, self).__init__(*args, **kwargs)

        self.client = Client(
            self.application.options.redis_host,
            self.application.options.redis_port,
            selected_db=self.application.options.redis_db
        )

        self.listen()

    @coroutine
    def listen(self):
        client = self.client

        client.connect()

        yield Task(
            client.subscribe,
            'stats'
        )

        self.client.listen(self.on_message)

    def on_message(self, msg):
        if msg.kind == 'message':
            self.write_message(str(msg.body))

        if msg.kind == 'disconnect':
            self.close()

    def on_close(self):
        client = self.client
        if client.subscribed:
            client.unsubscribe('stats')
            client.disconnect()

    def check_origin(self, origin):
        if self.application.debug:
            return True

        return super(StatsWSHandler, self).check_origin(origin)
Example #11
0
    def __init__(self, command_options):
        self.options = command_options

        handlers = self.get_handlers()

        super(Klassify, self).__init__(handlers, debug=self.debug)

        self.redis = Client(options.redis_host,
                            options.redis_port,
                            selected_db=options.redis_db)
Example #12
0
 def __init__(self, **kwargs):
     routes = [
         (r'/socket', SprintHandler),
         (r'/(?P<model>task|sprint|user)/(?P<pk>[0-9]+)', UpdateHandler),
     ]
     super().__init__(routes, **kwargs)
     self.subscriber = RedisSubscriber(Client())
     self.publisher = Redis()
     self._key = os.environ.get('WATERCOOLER_SECRET',
                                '3121556214dsa654d6asdas')
     self.signer = TimestampSigner(self._key)
Example #13
0
    def __init__(self, **kwargs):

        routes = [
            (r'/api/socket', ChatHandler),
            (r'/chat/(?P<pk>[0-9]+)', UpdateHandler),
        ]
        super(ChatApplication, self).__init__(routes, **kwargs)
        self.subscriber = RedisSubscriber(Client())
        self.publisher = Redis()
        self._key = os.environ.get('TORNADOAPP_SECRET',
                                   'TAfs7y8ajbahvct5r56465avhdsadsg')
        self.signer = TimestampSigner(self._key)
Example #14
0
 def __init__(self, **kwargs):
     routes = [
         (r'/socket', SprintHandler),
         (r'/(?P<model>task|sprint|user)/(?P<pk>[0-9]+)', UpdateHandler),
     ]
     super().__init__(routes, **kwargs)
     self.subscriber = RedisSubscriber(Client())
     self.publisher = Redis()
     # 该密钥必须要与Django应用使用的密钥相一致
     self._key = os.environ.get('WATERCOOLER_SECRET', 
         '-i-w&oe-jmzpfc#88i=u+)*9sw21es0x*^om5_6ncb%16k2gtm')
     self.signer = TimestampSigner(self._key)
Example #15
0
 def __init__(self, **kwargs):
     routes = [
         (r'/socket', SprintHandler),
         (r'/(?P<model>task|sprint|user)/(?P<pk>[0-9]+)', UpdateHandler),
     ]
     super().__init__(routes, **kwargs)
     self.subscriber = RedisSubscriber(Client())
     self.publisher = Redis()
     self._key = os.environ.get(
         'WATERCOOLER_SECRET',
         ')zu-07tfvq5&@f^k26f&c58w+w$q=r#ttx!j6pku(-lj6d3jtv')
     self.signer = TimestampSigner(self._key)
Example #16
0
 def __init__(self, **kwargs):
     route = [
         (r'/(?P<sprint>[0-9]+)', SprintHandler),
         (r'/(?P<model>task|sprint|user)/(?P<pk>[0-9]+)', UpdateHandler),
     ]
     super().__init__(route, **kwargs)
     # self.subscriptions = defaultdict(list)
     self.subscriber = RedisSubscriber(Client())
     self.publisher = Redis()
     self._key = os.environ.get(
         'WATERCOOLER_SECRET',
         'pTyz1dzMeVUGrb0Su4QXsP984qTlvQRHpFnnlHuH')  ###随机设置一个共享密令
     self.signer = TimestampSigner(self._key)
Example #17
0
 def __init__(self, *args, **kwargs):
     self.closed_channels = None
     super(MessagesHandler, self).__init__()
     self.webrtc_ids = {}
     self.id = None  # child init
     self.sex = None
     self.last_client_ping = 0
     self.sender_name = None
     self.user_id = 0  # anonymous by default
     self.ip = None
     from chat import global_redis
     self.async_redis_publisher = global_redis.async_redis_publisher
     self.sync_redis = global_redis.sync_redis
     self.channels = []
     self._logger = None
     self.async_redis = Client(host=REDIS_HOST, port=REDIS_PORT)
     self.patch_tornadoredis()
     # input websocket messages handlers
     # The handler is determined by @VarNames.EVENT
     self.process_ws_message = {
         Actions.GET_MESSAGES: self.process_get_messages,
         Actions.SEND_MESSAGE: self.process_send_message,
         Actions.CREATE_DIRECT_CHANNEL: self.create_user_channel,
         Actions.DELETE_ROOM: self.delete_channel,
         Actions.EDIT_MESSAGE: self.edit_message,
         Actions.CREATE_ROOM_CHANNEL: self.create_new_room,
         Actions.INVITE_USER: self.invite_user,
         Actions.PING: self.respond_ping,
         Actions.PONG: self.process_pong_message,
     }
     # Handlers for redis messages, if handler returns true - message won't be sent to client
     # The handler is determined by @VarNames.EVENT
     self.process_pubsub_message = {
         Actions.CREATE_DIRECT_CHANNEL: self.send_client_new_channel,
         Actions.CREATE_ROOM_CHANNEL: self.send_client_new_channel,
         Actions.DELETE_ROOM: self.send_client_delete_channel,
         Actions.INVITE_USER: self.send_client_new_channel,
         Actions.PING: self.process_ping_message,
     }
Example #18
0
class MessageHandler(WebSocketHandler):
    def __init__(self, *args, **kwargs):
        super(MessageHandler, self).__init__(*args, **kwargs)
        self.redis = Client()
        self.redis.connect()

    def get_current_user(self):
        user = self.get_secure_cookie("user")
        if user is None:
            return ''
        else:
            return user.strip('" ')

    def on_message(self, msg):
        msginfo = loads(msg)
        #listens for handshake from page
        if "user:" in msginfo['msg']:
            self.channel = msginfo['msg'].split(':')[1]
            #need to split the rest off to new func so it can be asynchronous
            self.listen()

    # Decorator turns the function into an asynchronous generator object
    @tornado.gen.engine
    def listen(self):
        #runs task given, with the yield required to get returned value
        #equivalent of callback/wait pairing from tornado.gen
        yield tornado.gen.Task(self.redis.subscribe, self.channel)
        if not self.redis.subscribed:
            self.write_message('ERROR IN SUBSCRIPTION')
        #listen from tornadoredis makes the listen object asynchronous
        #if using standard redis lib, it blocks while listening
        self.redis.listen(self.callback)
        # Try and fight race condition by loading from redis after listen
        # started need to use std redis lib because tornadoredis is in
        # subscribed state
        oldmessages = r_server.lrange(self.channel + ':messages', 0, -1)
        if oldmessages is not None:
            for message in oldmessages:
                self.write_message(message)

    def callback(self, msg):
        if msg.kind == 'message':
            self.write_message(str(msg.body))

    @tornado.gen.engine
    def on_close(self):
        yield tornado.gen.Task(self.redis.unsubscribe, self.channel)
        self.redis.disconnect()
Example #19
0
class MessageHandler(WebSocketHandler):
    def __init__(self, *args, **kwargs):
        super(MessageHandler, self).__init__(*args, **kwargs)
        self.redis = Client()
        self.redis.connect()

    def get_current_user(self):
        user = self.get_secure_cookie("user")
        if user == None:
            return ''
        else:
            return user.strip('" ')

    def on_message(self, msg):
        msginfo = loads(msg)
        #listens for handshake from page
        if "user:" in msginfo['msg']:
            self.channel = msginfo['msg'].split(':')[1]
            #need to split the rest off to new func so it can be asynchronous
            self.listen()

    #decorator turns the function into an asynchronous generator object
    @tornado.gen.engine
    def listen(self):
        #runs task given, with the yield required to get returned value
        #equivalent of callback/wait pairing from tornado.gen
        yield tornado.gen.Task(self.redis.subscribe, self.channel)
        if not self.redis.subscribed:
            self.write_message('ERROR IN SUBSCRIPTION')
        #listen from tornadoredis makes the listen object asynchronous
        #if using standard redis lib, it blocks while listening
        self.redis.listen(self.callback)
        #try and fight race condition by loading from redis after listen started
        #need to use std redis lib because tornadoredis is in subscribed state
        oldmessages = r_server.lrange(self.channel + ':messages', 0, -1)
        if oldmessages != None:
            for message in oldmessages:
                self.write_message(message)

    def callback(self, msg):
        if msg.kind == 'message':
            self.write_message(str(msg.body))

    @tornado.gen.engine
    def on_close(self):
        yield tornado.gen.Task(self.redis.unsubscribe, self.channel)
        self.redis.disconnect()
Example #20
0
class MessagesHandler(MessagesCreator):
    def __init__(self, *args, **kwargs):
        self.closed_channels = None
        super(MessagesHandler, self).__init__()
        self.webrtc_ids = {}
        self.id = None  # child init
        self.sex = None
        self.last_client_ping = 0
        self.sender_name = None
        self.user_id = 0  # anonymous by default
        self.ip = None
        from chat import global_redis
        self.async_redis_publisher = global_redis.async_redis_publisher
        self.sync_redis = global_redis.sync_redis
        self.channels = []
        self._logger = None
        self.async_redis = Client(host=REDIS_HOST, port=REDIS_PORT)
        self.patch_tornadoredis()
        # input websocket messages handlers
        # The handler is determined by @VarNames.EVENT
        self.process_ws_message = {
            Actions.GET_MESSAGES: self.process_get_messages,
            Actions.SEND_MESSAGE: self.process_send_message,
            Actions.CREATE_DIRECT_CHANNEL: self.create_user_channel,
            Actions.DELETE_ROOM: self.delete_channel,
            Actions.EDIT_MESSAGE: self.edit_message,
            Actions.CREATE_ROOM_CHANNEL: self.create_new_room,
            Actions.INVITE_USER: self.invite_user,
            Actions.PING: self.respond_ping,
            Actions.PONG: self.process_pong_message,
        }
        # Handlers for redis messages, if handler returns true - message won't be sent to client
        # The handler is determined by @VarNames.EVENT
        self.process_pubsub_message = {
            Actions.CREATE_DIRECT_CHANNEL: self.send_client_new_channel,
            Actions.CREATE_ROOM_CHANNEL: self.send_client_new_channel,
            Actions.DELETE_ROOM: self.send_client_delete_channel,
            Actions.INVITE_USER: self.send_client_new_channel,
            Actions.PING: self.process_ping_message,
        }

    def patch_tornadoredis(self):  # TODO remove this
        fabric = type(self.async_redis.connection.readline)
        self.async_redis.connection.old_read = self.async_redis.connection.readline

        def new_read(new_self, callback=None):
            try:
                return new_self.old_read(callback=callback)
            except Exception as e:
                return
                current_online = self.get_online_from_redis(
                    RedisPrefix.DEFAULT_CHANNEL)
                self.logger.error(e)
                self.logger.error(
                    "Exception info: "
                    "self.id: %s ;;; "
                    "self.connected = '%s';;; "
                    "Redis default channel online = '%s';;; "
                    "self.channels = '%s';;; "
                    "self.closed_channels  = '%s';;;", self.id, self.connected,
                    current_online, self.channels, self.closed_channels)
                raise e

        self.async_redis.connection.readline = fabric(
            new_read, self.async_redis.connection)

    @property
    def connected(self):
        raise NotImplemented

    @connected.setter
    def connected(self, value):
        raise NotImplemented

    @property
    def http_client(self):
        raise NotImplemented

    @engine
    def listen(self, channels):
        yield Task(self.async_redis.subscribe, channels)
        self.async_redis.listen(self.on_pub_sub_message)

    @property
    def logger(self):
        return self._logger if self._logger else base_logger

    @engine
    def add_channel(self, channel):
        self.channels.append(channel)
        yield Task(self.async_redis.subscribe, (channel, ))

    def get_online_from_redis(self, channel):
        return self.get_online_and_status_from_redis(channel)[1]

    def get_online_and_status_from_redis(self, channel):
        """
		:rtype : (bool, list)
		"""
        online = self.sync_redis.ssmembers(channel)
        self.logger.debug('!! channel %s redis online: %s', channel, online)
        return self.parse_redis_online(online) if online else (False, [])

    def parse_redis_online(self, online):
        """
		:rtype : (bool, list)
		"""
        result = set()
        user_is_online = False
        for decoded in online:  # py2 iteritems
            # : char specified in cookies_middleware.py.create_id
            user_id = int(decoded.split(':')[0])
            if user_id == self.user_id and decoded != self.id:
                user_is_online = True
            result.add(user_id)
        return user_is_online, list(result)

    def add_online_user(self, room_id, is_online, online):
        """
		adds to redis
		online_users = { connection_hash1 = stored_redis_user1, connection_hash_2 = stored_redis_user2 }
		:return: if user is online
		"""
        if is_online:  # Send user names to self
            online_user_names_mes = self.room_online(online,
                                                     Actions.REFRESH_USER,
                                                     room_id)
            self.logger.info('!! Second tab, retrieving online for self')
            self.ws_write(online_user_names_mes)
        else:  # if a new tab has been opened
            online.append(self.user_id)
            online_user_names_mes = self.room_online(online, Actions.LOGIN,
                                                     room_id)
            self.logger.info('!! First tab, sending refresh online for all')
            self.publish(online_user_names_mes, room_id)

    def get_is_online(self, room_id):
        self.async_redis_publisher.sadd(room_id, self.id)
        # since we add user to online first, latest trigger will always show correct online
        return self.get_online_and_status_from_redis(room_id)

    def publish(self, message, channel, parsable=False):
        jsoned_mess = encode_message(message, parsable)
        self.logger.debug('<%s> %s', channel, jsoned_mess)
        self.async_redis_publisher.publish(channel, jsoned_mess)

    def on_pub_sub_message(self, message):
        """
		All pubsub messages are automatically sent to client.
		:param message:
		:return:
		"""
        data = message.body
        if isinstance(data, str_type):  # not subscribe event
            prefixless_str = remove_parsable_prefix(data)
            if prefixless_str:
                dict_message = json.loads(prefixless_str)
                res = self.process_pubsub_message[dict_message[
                    VarNames.EVENT]](dict_message)
                if not res:
                    self.ws_write(prefixless_str)
            else:
                self.ws_write(data)

    def ws_write(self, message):
        raise NotImplementedError('WebSocketHandler implements')

    @asynchronous
    def search_giphy(self, message, query, cb):
        self.logger.debug("!! Asking giphy for: %s", query)

        def on_giphy_reply(response):
            try:
                self.logger.debug("!! Got giphy response: " +
                                  str(response.body))
                res = json.loads(response.body)
                giphy = res['data'][0]['images']['downsized_medium']['url']
            except:
                giphy = None
            cb(message, giphy)

        url = GIPHY_URL.format(GIPHY_API_KEY, quote(query, safe=''))
        self.http_client.fetch(url, callback=on_giphy_reply)

    def notify_offline(self, channel, message_id):
        if FIREBASE_API_KEY is None:
            return
        online = self.get_online_from_redis(channel)
        if channel == ALL_ROOM_ID:
            return
        offline_users = RoomUsers.objects.filter(
            room_id=channel, notifications=True).exclude(
                user_id__in=online).values_list('user_id')
        subscriptions = Subscription.objects.filter(user__in=offline_users,
                                                    inactive=False)
        if len(subscriptions) == 0:
            return
        new_sub_mess = [
            SubscriptionMessages(message_id=message_id, subscription_id=r.id)
            for r in subscriptions
        ]
        reg_ids = [r.registration_id for r in subscriptions]
        SubscriptionMessages.objects.bulk_create(new_sub_mess)
        self.post_firebase(list(reg_ids))

    @asynchronous
    def post_firebase(self, reg_ids):
        def on_reply(response):
            try:
                self.logger.debug("!! FireBase response: " +
                                  str(response.body))
                response_obj = json.loads(response.body)
                delete = []
                for index, elem in enumerate(response_obj['results']):
                    if elem.get('error') in [
                            'NotRegistered', 'InvalidRegistration'
                    ]:
                        delete.append(reg_ids[index])
                if len(delete) > 0:
                    self.logger.info("Deactivating subscriptions: %s", delete)
                    Subscription.objects.filter(
                        registration_id__in=delete).update(inactive=True)
            except Exception as e:
                self.logger.error("Unable to parse response" + str(e))
                pass

        headers = {
            "Content-Type": "application/json",
            "Authorization": "key=%s" % FIREBASE_API_KEY
        }
        body = json.dumps({"registration_ids": reg_ids})
        self.logger.debug("!! post_fire_message %s", body)
        r = HTTPRequest(FIREBASE_URL,
                        method="POST",
                        headers=headers,
                        body=body)
        self.http_client.fetch(r, callback=on_reply)

    def isGiphy(self, content):
        if GIPHY_API_KEY is not None and content is not None:
            giphy_match = re.search(GIPHY_REGEX, content)
            return giphy_match.group(1) if giphy_match is not None else None

    def process_send_message(self, message):
        """
		:type message: dict
		"""
        content = message.get(VarNames.CONTENT)
        giphy_match = self.isGiphy(content)

        # @transaction.atomic mysql has gone away
        def send_message(message, giphy=None):
            files = UploadedFile.objects.filter(id__in=message.get(
                VarNames.FILES),
                                                user_id=self.user_id)
            symbol = get_max_key(files)
            channel = message[VarNames.CHANNEL]
            js_id = message[VarNames.JS_MESSAGE_ID]
            message_db = Message(sender_id=self.user_id,
                                 content=message[VarNames.CONTENT],
                                 symbol=symbol,
                                 giphy=giphy,
                                 room_id=channel)
            res_files = []
            do_db(message_db.save)
            if files:
                images = up_files_to_img(files, message_db.id)
                res_files = MessagesCreator.prepare_img_video(
                    images, message_db.id)
            prepared_message = self.create_send_message(
                message_db, Actions.PRINT_MESSAGE, res_files, js_id)
            self.publish(prepared_message, channel)
            self.notify_offline(channel, message_db.id)

        if giphy_match is not None:
            self.search_giphy(message, giphy_match, send_message)
        else:
            send_message(message)

    def create_new_room(self, message):
        room_name = message[VarNames.ROOM_NAME]
        if not room_name or len(room_name) > 16:
            raise ValidationError('Incorrect room name "{}"'.format(room_name))
        room = Room(name=room_name)
        do_db(room.save)
        RoomUsers(room_id=room.id, user_id=self.user_id).save()
        subscribe_message = self.subscribe_room_channel_message(
            room.id, room_name)
        self.publish(subscribe_message, self.channel, True)

    def invite_user(self, message):
        room_id = message[VarNames.ROOM_ID]
        user_id = message[VarNames.USER_ID]
        room = get_or_create_room(self.channels, room_id, user_id)
        users_in_room = {
            user.id: RedisPrefix.set_js_user_structure(user.username, user.sex)
            for user in room.users.all()
        }
        self.publish(
            self.add_user_to_room(room_id, user_id, users_in_room[user_id]),
            room_id)
        subscribe_message = self.invite_room_channel_message(
            room_id, user_id, room.name, users_in_room)
        self.publish(subscribe_message, RedisPrefix.generate_user(user_id),
                     True)

    def respond_ping(self, message):
        self.ws_write(self.responde_pong(message[VarNames.JS_MESSAGE_ID]))

    def process_pong_message(self, message):
        self.last_client_ping = message[VarNames.TIME]

    def process_ping_message(self, message):
        def call_check():
            if message[VarNames.TIME] != self.last_client_ping:
                self.close(408, "Ping timeout")

        IOLoop.instance().call_later(settings.PING_CLOSE_SERVER_DELAY,
                                     call_check)

    def create_user_channel(self, message):
        user_id = message[VarNames.USER_ID]
        room_id = create_room(self.user_id, user_id)
        subscribe_message = self.subscribe_direct_channel_message(
            room_id, user_id, self.user_id != user_id)
        self.publish(subscribe_message, self.channel, True)
        other_channel = RedisPrefix.generate_user(user_id)
        if self.channel != other_channel:
            self.publish(subscribe_message, other_channel, True)

    def delete_channel(self, message):
        room_id = message[VarNames.ROOM_ID]
        if room_id not in self.channels or room_id == ALL_ROOM_ID:
            raise ValidationError('You are not allowed to exit this room')
        room = do_db(Room.objects.get, id=room_id)
        if room.disabled:
            raise ValidationError('Room is already deleted')
        if room.name is None:  # if private then disable
            room.disabled = True
        else:  # if public -> leave the room, delete the link
            RoomUsers.objects.filter(room_id=room.id,
                                     user_id=self.user_id).delete()
            online = self.get_online_from_redis(room_id)
            online.remove(self.user_id)
            self.publish(self.room_online(online, Actions.LOGOUT, room_id),
                         room_id)
        room.save()
        message = self.unsubscribe_direct_message(room_id)
        self.publish(message, room_id, True)

    def edit_message(self, data):
        js_id = data[VarNames.JS_MESSAGE_ID]
        message = do_db(Message.objects.get, id=data[VarNames.MESSAGE_ID])
        validate_edit_message(self.user_id, message)
        message.content = data[VarNames.CONTENT]
        MessageHistory(message=message,
                       content=message.content,
                       giphy=message.giphy).save()
        message.edited_times += 1
        giphy_match = self.isGiphy(data[VarNames.CONTENT])
        if message.content is None:
            Message.objects.filter(id=data[VarNames.MESSAGE_ID]).update(
                deleted=True, edited_times=message.edited_times, content=None)
            self.publish(
                self.create_send_message(message, Actions.DELETE_MESSAGE, None,
                                         js_id), message.room_id)
        elif giphy_match is not None:
            self.edit_message_giphy(giphy_match, message, js_id)
        else:
            self.edit_message_edit(data, message, js_id)

    def edit_message_giphy(self, giphy_match, message, js_id):
        def edit_glyphy(message, giphy):
            do_db(Message.objects.filter(id=message.id).update,
                  content=message.content,
                  symbol=message.symbol,
                  giphy=giphy,
                  edited_times=message.edited_times)
            message.giphy = giphy
            self.publish(
                self.create_send_message(message, Actions.EDIT_MESSAGE, None,
                                         js_id), message.room_id)

        self.search_giphy(message, giphy_match, edit_glyphy)

    def edit_message_edit(self, data, message, js_id):
        action = Actions.EDIT_MESSAGE
        message.giphy = None
        files = UploadedFile.objects.filter(id__in=data.get(VarNames.FILES),
                                            user_id=self.user_id)
        if files:
            update_symbols(files, message)
            up_files_to_img(files, message.id)
        if message.symbol:  # fetch all, including that we just store
            db_images = Image.objects.filter(message_id=message.id)
            prep_files = MessagesCreator.prepare_img_video(
                db_images, message.id)
        else:
            prep_files = None
        Message.objects.filter(id=message.id).update(
            content=message.content,
            symbol=message.symbol,
            giphy=None,
            edited_times=message.edited_times)
        self.publish(
            self.create_send_message(message, action, prep_files, js_id),
            message.room_id)

    def send_client_new_channel(self, message):
        room_id = message[VarNames.ROOM_ID]
        self.add_channel(room_id)
        is_online, online = self.get_is_online(room_id=room_id)
        self.add_online_user(room_id, is_online, online)

    def send_client_delete_channel(self, message):
        room_id = message[VarNames.ROOM_ID]
        self.async_redis.unsubscribe((room_id, ))
        self.async_redis_publisher.hdel(room_id, self.id)
        self.channels.remove(room_id)

    def process_get_messages(self, data):
        """
		:type data: dict
		"""
        header_id = data.get(VarNames.GET_MESSAGES_HEADER_ID, None)
        count = int(data.get(VarNames.GET_MESSAGES_COUNT, 10))
        room_id = data[VarNames.CHANNEL]
        self.logger.info('!! Fetching %d messages starting from %s', count,
                         header_id)
        if header_id is None:
            messages = Message.objects.filter(
                room_id=room_id).order_by('-pk')[:count]
        else:
            messages = Message.objects.filter(
                Q(id__lt=header_id),
                Q(room_id=room_id)).order_by('-pk')[:count]
        imv = do_db(get_message_images_videos, messages)
        response = self.get_messages(messages, room_id, imv,
                                     MessagesCreator.prepare_img_video)
        self.ws_write(response)
Example #21
0
class ChatHandler(WebSocketHandler):

    def get_chat_user(self):
        return self.db.query(
            ChatUser, User, AnyChat,
        ).join(
            User, ChatUser.user_id == User.id,
        ).join(
            AnyChat, ChatUser.chat_id == AnyChat.id,
        ).filter(and_(
            ChatUser.user_id == self.user_id,
            ChatUser.chat_id == self.chat_id,
        )).one()

    def set_typing(self, is_typing):
        command = redis.sadd if is_typing else redis.srem
        typing_key = "chat:%s:typing" % self.chat_id
        if command(typing_key, self.user_number):
            redis.publish(self.channels["typing"], json.dumps({
                "typing": list(int(_) for _ in redis.smembers(typing_key)),
            }))

    def check_origin(self, origin):
        return origin_regex.match(origin) is not None

    def prepare(self):
        self.id = str(uuid4())
        self.joined = False
        try:
            self.session_id = self.cookies["session"].value
            self.chat_id = int(self.path_args[0])
            self.user_id = int(redis.get("session:%s" % self.session_id))
        except (KeyError, TypeError, ValueError):
            self.send_error(400)
            return
        self.db = sm()
        try:
            self.chat_user, self.user, self.chat = self.get_chat_user()
        except NoResultFound:
            self.send_error(404)
            return
        # Remember the user number so typing notifications can refer to it
        # without reopening the database session.
        self.user_number = self.chat_user.number
        self.user.last_online = datetime.now()
        self.user.last_ip = self.request.headers["X-Forwarded-For"]
        if self.user.group == "banned":
            self.send_error(403)
            return
        try:
            authorize_joining(redis, self.db, self)
        except (UnauthorizedException, BannedException, TooManyPeopleException):
            self.send_error(403)
            return

    def open(self, chat_id):
        sockets.add(self)
        redis.sadd("chat:%s:sockets:%s" % (self.chat_id, self.session_id), self.id)
        print "socket opened:", self.id, self.chat.url, self.user.username

        try:
            kick_check(redis, self)
        except KickedException:
            self.write_message(json.dumps({"exit": "kick"}))
            self.close()
            return

        # Subscribe
        self.channels = {
            "chat": "channel:%s" % self.chat_id,
            "user": "******" % (self.chat_id, self.user_id),
            "typing": "channel:%s:typing" % self.chat_id,
        }
        if self.chat.type == "pm":
            self.channels["pm"] = "channel:pm:%s" % self.user_id
        self.redis_listen()

        # Send backlog.
        try:
            after = int(self.request.query_arguments["after"][0])
        except (KeyError, IndexError, ValueError):
            after = 0
        messages = redis.zrangebyscore("chat:%s" % self.chat_id, "(%s" % after, "+inf")
        self.write_message(json.dumps({
            "chat": self.chat.to_dict(),
            "messages": [json.loads(_) for _ in messages],
        }))

        join_message_sent = join(redis, self.db, self)
        self.joined = True

        # Send userlist if nothing was sent by join().
        if not join_message_sent:
            self.write_message(json.dumps({"users": get_userlist(self.db, redis, self.chat)}))

        self.db.commit()

    def on_message(self, message):
        print "message:", message
        if message in ("typing", "stopped_typing"):
            self.set_typing(message == "typing")

    def on_close(self):
        # Unsubscribe here and let the exit callback handle disconnecting.
        if hasattr(self, "redis_client"):
            self.redis_client.unsubscribe(self.redis_client.subscribed)
        if self.joined and disconnect(redis, self.chat_id, self.id):
            try:
                send_quit_message(self.db, redis, *self.get_chat_user())
            except NoResultFound:
                send_userlist(self.db, redis, self.chat)
            self.db.commit()
        self.set_typing(False)
        print "socket closed:", self.id
        redis.srem("chat:%s:sockets:%s" % (self.chat_id, self.session_id), self.id)
        sockets.remove(self)

    def finish(self, *args, **kwargs):
        if hasattr(self, "db"):
            self.db.close()
            del self.db
        super(ChatHandler, self).finish(*args, **kwargs)

    @engine
    def redis_listen(self):
        self.redis_client = Client(
            host=os.environ["REDIS_HOST"],
            port=int(os.environ["REDIS_PORT"]),
            selected_db=int(os.environ["REDIS_DB"]),
        )
        yield Task(self.redis_client.subscribe, self.channels.values())
        self.redis_client.listen(self.on_redis_message, self.on_redis_unsubscribe)

    def on_redis_message(self, message):
        print "redis message:", message
        if message.kind != "message":
            return
        self.write_message(message.body)
        if message.channel == self.channels["user"]:
            data = json.loads(message.body)
            if "exit" in data:
                self.joined = False
                self.close()

    def on_redis_unsubscribe(self, callback):
        self.redis_client.disconnect()
Example #22
0
 def redis_listen(self):
     self.redis_client = Client(unix_socket_path=config.get("app:main", "cherubplay.socket_pubsub"))
     yield Task(self.redis_client.subscribe, self.redis_channels)
     self.redis_client.listen(self.on_redis_message, self.on_redis_unsubscribe)
Example #23
0
 def __init__(self, *args, **kwargs):
     super().__init__(*args, **kwargs)
     info = self.parse_redis_parameters()
     db_number = info.pop('db', 0)
     self.publisher = Redis(db=db_number, **info)
     self.subscriber = RedisSubscriber(Client(selected_db=db_number, **info))
Example #24
0
class ChatHandler(WebSocketHandler):

    user = None
    chat = None
    chat_user = None
    socket_id = None
    redis_channels = ()
    ignore_next_message = False
    redis_client = None

    def check_origin(self, origin):
        return origin == config.get("app:main", "cherubplay.socket_origin")

    def open(self, chat_url):
        sockets.add(self)
        with db_session() as db:
            self.user = get_user(db, self.cookies)
            if self.user is None:
                self.close()
                return

            self.chat = get_chat(db, chat_url)
            if self.chat is None:
                self.close()
                return

            self.chat_user = get_chat_user(db, self.chat.id, self.user.id)
            if self.chat_user is None:
                self.close()
                return

            self.socket_id = str(uuid4())

            # Fire online message, but only if this is the only tab we have open.
            online_handles = online_user_store.online_handles(self.chat)
            if self.chat_user.handle not in online_handles:
                publish_client.publish(pubsub_channel(self.chat), json.dumps({
                    "action": "online",
                    "handle": self.chat_user.handle,
                }))

            # See if anyone else is online.
            online_handles_except_self = online_handles - {self.chat_user.handle}
            if len(online_handles_except_self) == 1:
                self.write_message({"action": "online", "handle": next(iter(online_handles_except_self))})
            elif len(online_handles_except_self) >= 1:
                self.write_message({
                    "action": "online_list",
                    "handles": sorted(list(online_handles)),
                })

            online_user_store.connect(self.chat_user, self.socket_id)

            self.redis_channels = (
                pubsub_channel(self.chat),
                pubsub_channel(self.user),
                pubsub_channel(self.chat_user),
            )

            self.redis_listen()
            # Send the backlog if necessary.
            if "after" in self.request.query_arguments:
                try:
                    after = int(self.request.query_arguments["after"][0])
                except ValueError:
                    return
                for message in (
                    db.query(Message)
                    .options(joinedload(Message.chat_user))
                    .filter(and_(Message.chat_id == self.chat.id, Message.id > after))
                ):
                    self.write_message({
                        "action": "message",
                        "message": {
                            "id":     message.id,
                            "type":   message.type.value,
                            "colour": message.colour,
                            "symbol": message.symbol_character,
                            "name":   message.chat_user.name if message.chat_user else None,
                            "text":   message.text.as_plain_text(),
                            "html":   message.text.as_html(),
                        }
                    })

    def on_message(self, message_string):
        message = json.loads(message_string)
        if message["action"] in ("typing", "stopped_typing"):
            publish_client.publish(pubsub_channel(self.chat), json.dumps({
                "action": message["action"],
                "handle": self.chat_user.handle,
            }))
            # Ignore our own typing messages.
            self.ignore_next_message = True

    def on_close(self):
        # Unsubscribe here and let the exit callback handle disconnecting.
        if self.redis_client:
            self.redis_client.unsubscribe(self.redis_channels)
        online_user_store.disconnect(self.chat, self.socket_id)
        # Fire offline message, but only if we don't have any other tabs open.
        if self.chat_user.handle not in online_user_store.online_handles(self.chat):
            publish_client.publish(pubsub_channel(self.chat), json.dumps({
                "action": "offline",
                "handle": self.chat_user.handle,
            }))
        sockets.remove(self)

    @engine
    def redis_listen(self):
        self.redis_client = Client(unix_socket_path=config.get("app:main", "cherubplay.socket_pubsub"))
        yield Task(self.redis_client.subscribe, self.redis_channels)
        self.redis_client.listen(self.on_redis_message, self.on_redis_unsubscribe)

    def on_redis_message(self, message):
        if message.kind == "message":
            if message.body == "kicked":
                self.write_message("{\"action\":\"kicked\"}")
                self.close()
            elif not self.ignore_next_message:
                self.write_message(message.body)
            else:
                self.ignore_next_message = False

    def on_redis_unsubscribe(self, callback):
        self.redis_client.disconnect()
Example #25
0
 def __init__(self):
     worker_logger.info('Initialized Redis Subscriber instance')
     self._client = Client(host=REDIS_HOST, port=int(REDIS_PORT))
     self._subscribers = {}
     self._listen()
Example #26
0
 def connect_redis():
     rds = Client(host=settings.REDIS['Master']['host'],
                  port=settings.REDIS['Master']['port'],
                  selected_db=settings.REDIS['Master']['db'])
     rds.connect()
     return rds
Example #27
0
 def __init__(self, *args, **kwargs):
     super(MessageHandler, self).__init__(*args, **kwargs)
     self.redis = Client()
     self.redis.connect()
Example #28
0
 def redis_listen(self):
     self.redis_client = Client(unix_socket_path=config.get("app:main", "cherubplay.socket_pubsub"))
     yield Task(self.redis_client.subscribe, "chat:"+str(self.chat.id))
     self.redis_client.listen(self.on_redis_message, self.on_redis_unsubscribe)
Example #29
0
class MessagesHandler(MessagesCreator):
    def __init__(self, *args, **kwargs):
        self.closed_channels = None
        super(MessagesHandler, self).__init__()
        self.webrtc_ids = {}
        self.id = None  # child init
        self.last_client_ping = 0
        self.user_id = 0  # anonymous by default
        self.ip = None
        from chat import global_redis
        self.async_redis_publisher = global_redis.async_redis_publisher
        self.sync_redis = global_redis.sync_redis
        self.channels = []
        self._logger = None
        self.async_redis = Client(host=REDIS_HOST,
                                  port=REDIS_PORT,
                                  selected_db=REDIS_DB)
        self.patch_tornadoredis()
        # input websocket messages handlers
        # The handler is determined by @VarNames.EVENT
        self.process_ws_message = {
            Actions.GET_MESSAGES: self.process_get_messages,
            Actions.PRINT_MESSAGE: self.process_send_message,
            Actions.DELETE_ROOM: self.delete_room,
            Actions.EDIT_MESSAGE: self.edit_message,
            Actions.CREATE_ROOM: self.create_new_room,
            Actions.CREATE_CHANNEL: self.create_new_channel,
            Actions.SAVE_CHANNEL_SETTINGS: self.save_channels_settings,
            Actions.SAVE_ROOM_SETTINGS: self.save_room_settings,
            Actions.DELETE_CHANNEL: self.delete_channel,
            Actions.SET_USER_PROFILE: self.profile_save_user,
            Actions.SET_SETTINGS: self.profile_save_settings,
            Actions.INVITE_USER: self.invite_user,
            Actions.PING: self.respond_ping,
            Actions.PONG: self.process_pong_message
        }
        # Handlers for redis messages, if handler returns true - message won't be sent to client
        # The handler is determined by @VarNames.EVENT
        self.process_pubsub_message = {
            Actions.CREATE_ROOM: self.send_client_new_channel,
            Actions.DELETE_ROOM: self.send_client_delete_channel,
            Actions.INVITE_USER: self.send_client_new_channel,
            Actions.ADD_INVITE: self.send_client_new_channel,
            Actions.PING: self.process_ping_message,
        }

    def patch_tornadoredis(self):  # TODO remove this
        fabric = type(self.async_redis.connection.readline)
        self.async_redis.connection.old_read = self.async_redis.connection.readline

        def new_read(new_self, callback=None):
            try:
                return new_self.old_read(callback=callback)
            except Exception as e:
                return
                current_online = self.get_online_from_redis()
                self.logger.error(e)
                self.logger.error(
                    "Exception info: "
                    "self.id: %s ;;; "
                    "self.connected = '%s';;; "
                    "Redis default channel online = '%s';;; "
                    "self.channels = '%s';;; "
                    "self.closed_channels  = '%s';;;", self.id, self.connected,
                    current_online, self.channels, self.closed_channels)
                raise e

        self.async_redis.connection.readline = fabric(
            new_read, self.async_redis.connection)

    @property
    def connected(self):
        raise NotImplemented

    @connected.setter
    def connected(self, value):
        raise NotImplemented

    @gen.engine
    def listen(self, channels):
        yield Task(self.async_redis.subscribe, channels)
        self.async_redis.listen(self.on_pub_sub_message)

    @property
    def logger(self):
        return self._logger if self._logger else base_logger

    @gen.engine
    def add_channel(self, channel):
        self.channels.append(channel)
        yield Task(self.async_redis.subscribe, (channel, ))

    def get_online_from_redis(self):
        return self.get_online_and_status_from_redis()[1]

    def get_dict_users_from_redis(self):
        online = self.sync_redis.ssmembers(RedisPrefix.ONLINE_VAR)
        self.logger.debug('!! redis online: %s', online)
        result = self.parse_redis_online_into_dict_set(
            online) if online else {}
        return result

    @staticmethod
    def parse_redis_online_into_dict_set(online):
        """
		:rtype : Dict[int, set]
		"""
        result = {}
        for decoded in online:  # py2 iteritems
            user_id = decoded.split(':')[0]
            result.setdefault(int(user_id), []).append(decoded)
        return result

    def get_online_and_status_from_redis(self):
        """
		:rtype : (bool, list)
		"""
        online = self.sync_redis.ssmembers(RedisPrefix.ONLINE_VAR)
        self.logger.debug('!! redis online: %s', online)
        return self.parse_redis_online(online) if online else (False, [])

    def parse_redis_online(self, online):
        """
		:rtype : (bool, list)
		"""
        result = set()
        user_is_online = False
        for decoded in online:  # py2 iteritems
            user_id = int(decoded.split(':')[0])
            if user_id == self.user_id and decoded != self.id:
                user_is_online = True
            result.add(user_id)
        return user_is_online, list(result)

    def publish(self, message, channel, parsable=False):
        jsoned_mess = encode_message(message, parsable)
        self.raw_publish(jsoned_mess, channel)

    def raw_publish(self, jsoned_mess, channel):
        self.logger.debug('<%s> %s', channel, jsoned_mess)
        self.async_redis_publisher.publish(channel, jsoned_mess)

    def on_pub_sub_message(self, message):
        """
		All pubsub messages are automatically sent to client.
		:param message:
		:return:
		"""
        data = message.body
        if isinstance(data, str_type):  # not subscribe event
            prefixless_str = remove_parsable_prefix(data)
            if prefixless_str:
                dict_message = json.loads(prefixless_str)
                res = self.process_pubsub_message[dict_message[
                    VarNames.EVENT]](dict_message)
                if not res:
                    self.ws_write(prefixless_str)
            else:
                self.ws_write(data)

    def ws_write(self, message):
        raise NotImplementedError('WebSocketHandler implements')

    def search_giphy(self, message, query, cb):
        self.logger.debug("!! Asking giphy for: %s", query)

        def on_giphy_reply(response):
            try:
                self.logger.debug("!! Got giphy response: " +
                                  str(response.body))
                res = json.loads(response.body)
                giphy = res['data'][0]['images']['downsized_medium']['url']
            except:
                giphy = None
            cb(message, giphy)

        url = GIPHY_URL.format(GIPHY_API_KEY, quote(query, safe=''))
        http_client.fetch(url, callback=on_giphy_reply)

    def notify_offline(self, channel, message_id):
        if FIREBASE_API_KEY is None:
            return
        online = self.get_online_from_redis()
        if channel == ALL_ROOM_ID:
            return
        offline_users = RoomUsers.objects.filter(
            room_id=channel, notifications=True).exclude(
                user_id__in=online).values_list('user_id')
        subscriptions = Subscription.objects.filter(user__in=offline_users,
                                                    inactive=False)
        if len(subscriptions) == 0:
            return
        new_sub_mess = [
            SubscriptionMessages(message_id=message_id, subscription_id=r.id)
            for r in subscriptions
        ]
        reg_ids = [r.registration_id for r in subscriptions]
        SubscriptionMessages.objects.bulk_create(new_sub_mess)
        self.post_firebase(list(reg_ids))

    def post_firebase(self, reg_ids):
        def on_reply(response):
            try:
                self.logger.debug("!! FireBase response: " +
                                  str(response.body))
                response_obj = json.loads(response.body)
                delete = []
                for index, elem in enumerate(response_obj['results']):
                    if elem.get('error') in [
                            'NotRegistered', 'InvalidRegistration'
                    ]:
                        delete.append(reg_ids[index])
                if len(delete) > 0:
                    self.logger.info("Deactivating subscriptions: %s", delete)
                    Subscription.objects.filter(
                        registration_id__in=delete).update(inactive=True)
            except Exception as e:
                self.logger.error("Unable to parse response" + str(e))
                pass

        headers = {
            "Content-Type": "application/json",
            "Authorization": "key=%s" % FIREBASE_API_KEY
        }
        body = json.dumps({"registration_ids": reg_ids})
        self.logger.debug("!! post_fire_message %s", body)

        # TODO
        # webpush(subscription_info,
        # 		  data,
        # 		  vapid_private_key="Private Key or File Path[1]",
        # 		  vapid_claims={"sub": "mailto:YourEmailAddress"})

        r = HTTPRequest(FIREBASE_URL,
                        method="POST",
                        headers=headers,
                        body=body)
        http_client.fetch(r, callback=on_reply)

    def isGiphy(self, content):
        if GIPHY_API_KEY is not None and content is not None:
            giphy_match = re.search(GIPHY_REGEX, content)
            return giphy_match.group(1) if giphy_match is not None else None

    def process_send_message(self, message):
        """
		:type message: dict
		"""
        content = message.get(VarNames.CONTENT)
        giphy_match = self.isGiphy(content)

        # @transaction.atomic mysql has gone away
        def send_message(message, giphy=None):
            if message[VarNames.TIME_DIFF] < 0:
                raise ValidationError("Back to the future?")
            files = UploadedFile.objects.filter(id__in=message.get(
                VarNames.FILES),
                                                user_id=self.user_id)
            symbol = get_max_key(files)
            channel = message[VarNames.ROOM_ID]
            js_id = message[VarNames.JS_MESSAGE_ID]
            message_db = Message(sender_id=self.user_id,
                                 content=message[VarNames.CONTENT],
                                 symbol=symbol,
                                 giphy=giphy,
                                 room_id=channel)
            message_db.time -= message[VarNames.TIME_DIFF]
            res_files = []
            message_db.save()
            if files:
                images = up_files_to_img(files, message_db.id)
                res_files = MessagesCreator.prepare_img_video(
                    images, message_db.id)
            prepared_message = self.create_send_message(
                message_db,
                Actions.PRINT_MESSAGE,
                res_files,
            )
            prepared_message[VarNames.JS_MESSAGE_ID] = js_id
            self.publish(prepared_message, channel)
            self.notify_offline(channel, message_db.id)

        if giphy_match is not None:
            self.search_giphy(message, giphy_match, send_message)
        else:
            send_message(message)

    def save_channels_settings(self, message):
        channel_id = message[VarNames.CHANNEL_ID]
        channel_name = message[VarNames.CHANNEL_NAME]
        new_creator = message[VarNames.CHANNEL_CREATOR_ID]
        if not channel_name or len(channel_name) > 16:
            raise ValidationError(
                'Incorrect channel name name "{}"'.format(channel_name))
        channel = Channel.objects.get(id=channel_id)
        users_id = list(
            RoomUsers.objects.filter(room__channel_id=channel_id).values_list(
                'user_id', flat=True))
        if channel.creator_id != self.user_id and self.user_id not in users_id:
            raise ValidationError("You are not allowed to edit this channel")
        if new_creator != channel.creator_id:
            if self.user_id != channel.creator_id:
                raise ValidationError(
                    "Only admin can change the admin of this channel")
            if new_creator not in users_id:
                raise ValidationError(
                    "You can only change admin to one of the users in this channels room"
                )
            channel.creator_id = new_creator
        if self.user_id not in users_id:  # if channel has no rooms
            users_id.append(self.user_id)
        channel.name = channel_name
        channel.save()
        m = {
            VarNames.EVENT: Actions.SAVE_CHANNEL_SETTINGS,
            VarNames.CHANNEL_ID: channel_id,
            VarNames.CB_BY_SENDER: self.id,
            VarNames.HANDLER_NAME: HandlerNames.CHANNELS,
            VarNames.CHANNEL_NAME: channel_name,
            VarNames.CHANNEL_CREATOR_ID: channel.creator_id,
            VarNames.TIME: get_milliseconds(),
            VarNames.JS_MESSAGE_ID: message[VarNames.JS_MESSAGE_ID],
        }
        for user_id in users_id:
            self.publish(m, RedisPrefix.generate_user(user_id))

    def create_new_channel(self, message):
        channel_name = message.get(VarNames.CHANNEL_NAME)
        if not channel_name or len(channel_name) > 16:
            raise ValidationError(
                'Incorrect channel name name "{}"'.format(channel_name))

        channel = Channel(name=channel_name, creator_id=self.user_id)
        channel.save()
        channel_id = channel.id
        m = {
            VarNames.EVENT: Actions.CREATE_CHANNEL,
            VarNames.CHANNEL_ID: channel_id,
            VarNames.CHANNEL_CREATOR_ID: channel.creator_id,
            VarNames.CB_BY_SENDER: self.id,
            VarNames.HANDLER_NAME: HandlerNames.CHANNELS,
            VarNames.CHANNEL_NAME: channel_name,
            VarNames.TIME: get_milliseconds(),
            VarNames.JS_MESSAGE_ID: message[VarNames.JS_MESSAGE_ID],
        }
        self.publish(m, self.channel)

    def create_new_room(self, message):
        room_name = message.get(VarNames.ROOM_NAME)
        users = message.get(VarNames.ROOM_USERS)
        channel_id = message.get(VarNames.CHANNEL_ID)
        users.append(self.user_id)
        users = list(set(users))
        if room_name and len(room_name) > 16:
            raise ValidationError('Incorrect room name "{}"'.format(room_name))
        create_room = True
        if not room_name and len(users) == 2:
            user_rooms = evaluate(
                Room.users.through.objects.filter(
                    user_id=self.user_id,
                    room__name__isnull=True).values('room_id'))
            user_id = users[0] if users[1] == self.user_id else users[1]
            try:
                room = RoomUsers.objects.filter(user_id=user_id,
                                                room__in=user_rooms).values(
                                                    'room__id',
                                                    'room__disabled').get()
                room_id = room['room__id']
                if room['room__disabled']:  # only a private room can be disabled
                    Room.objects.filter(id=room_id).update(
                        disabled=False, p2p=message[VarNames.P2P])
                    RoomUsers.objects.filter(
                        user_id=self.user_id, room_id=room_id).update(
                            volume=message[VarNames.VOLUME],
                            notifications=message[VarNames.NOTIFICATIONS])
                else:
                    raise ValidationError('This room already exist')
                create_room = False
            except RoomUsers.DoesNotExist:
                pass
        elif not room_name:
            raise ValidationError(
                'At least one user should be selected, or room should be public'
            )

        if channel_id and channel_id not in self.get_users_channels_ids():
            raise ValidationError("You don't have access to this channel")
        if channel_id:
            channel = Channel.objects.get(id=channel_id)
            channel_name = channel.name
            channel_creator_id = channel.creator_id
        else:
            channel_name = None
            channel_creator_id = None
        if create_room:
            room = Room(name=room_name,
                        channel_id=channel_id,
                        p2p=message[VarNames.P2P])
            if not room_name:
                room.creator_id = self.user_id
            room.save()
            room_id = room.id
            max_id = Message.objects.all().aggregate(Max('id'))['id__max']
            ru = [
                RoomUsers(user_id=user_id,
                          room_id=room_id,
                          last_read_message_id=max_id,
                          volume=message[VarNames.VOLUME],
                          notifications=message[VarNames.NOTIFICATIONS])
                for user_id in users
            ]
            RoomUsers.objects.bulk_create(ru)

        m = {
            VarNames.EVENT: Actions.CREATE_ROOM,
            VarNames.ROOM_ID: room_id,
            VarNames.ROOM_USERS: users,
            VarNames.CB_BY_SENDER: self.id,
            VarNames.INVITER_USER_ID: self.user_id,
            VarNames.HANDLER_NAME: HandlerNames.CHANNELS,
            VarNames.VOLUME: message[VarNames.VOLUME],
            VarNames.P2P: message[VarNames.P2P],
            VarNames.NOTIFICATIONS: message[VarNames.NOTIFICATIONS],
            VarNames.ROOM_NAME: room_name,
            VarNames.TIME: get_milliseconds(),
            VarNames.JS_MESSAGE_ID: message[VarNames.JS_MESSAGE_ID],
        }
        if channel_id:
            m[VarNames.CHANNEL_NAME] = channel_name
            m[VarNames.CHANNEL_ID] = channel_id
            m[VarNames.CHANNEL_CREATOR_ID] = channel_creator_id
        jsoned_mess = encode_message(m, True)
        for user in users:
            self.raw_publish(jsoned_mess, RedisPrefix.generate_user(user))

    def save_room_settings(self, message):
        """
		POST only, validates email during registration
		"""
        room_id = message[VarNames.ROOM_ID]
        room_name = message[VarNames.ROOM_NAME]
        creator_id = message.get(
            VarNames.ROOM_CREATOR_ID)  # will be none for private room
        updated = RoomUsers.objects.filter(
            room_id=room_id, user_id=self.user_id).update(
                volume=message[VarNames.VOLUME],
                notifications=message[VarNames.NOTIFICATIONS])
        if updated != 1:
            raise ValidationError("You don't have access to this room")
        room = Room.objects.get(id=room_id)
        update_all = False
        if not room.name:
            if room.p2p != message[VarNames.P2P]:
                room.p2p = message[VarNames.P2P]
                update_all = True
        elif room_id != settings.ALL_ROOM_ID:
            if room_name != room.name:
                room.name = room_name
                update_all = True

            if room.channel_id != message[VarNames.CHANNEL_ID]:
                room.channel_id = message[VarNames.CHANNEL_ID]
                if room.channel_id and room.channel_id not in self.get_users_channels_ids(
                ):
                    raise ValidationError(
                        "You don't have access to this channel")
                update_all = True
            if creator_id != room.creator_id:
                if room.creator_id != self.user_id:
                    raise ValidationError(
                        "Only an owner of this room can change its admin")
                users_id = RoomUsers.objects.filter(
                    room_id=room.id).values_list('user_id', flat=True)
                if creator_id not in users_id:
                    raise ValidationError(
                        "You can only change admin to one of the users in this channels room"
                    )
                room.creator_id = creator_id
                update_all = True
        if message.get(VarNames.CHANNEL_ID):  # will be nOne for private room
            channel = Channel.objects.get(id=message[VarNames.CHANNEL_ID])
            channel_name = channel.name
            channel_creator_id = channel.creator_id
        else:
            channel_name = None
            channel_creator_id = None
        if update_all:
            room.save()
            room_users = list(RoomUsers.objects.filter(room_id=room_id))
            for room_user in room_users:
                self.publish(
                    {
                        VarNames.EVENT: Actions.SAVE_ROOM_SETTINGS,
                        VarNames.CHANNEL_ID: room.channel_id,
                        VarNames.CB_BY_SENDER: self.id,
                        VarNames.HANDLER_NAME: HandlerNames.CHANNELS,
                        VarNames.CHANNEL_NAME: channel_name,
                        VarNames.CHANNEL_CREATOR_ID: channel_creator_id,
                        VarNames.ROOM_CREATOR_ID: room.creator_id,
                        VarNames.ROOM_ID: room.id,
                        VarNames.VOLUME: room_user.volume,
                        VarNames.NOTIFICATIONS: room_user.notifications,
                        VarNames.P2P: message[VarNames.P2P],
                        VarNames.ROOM_NAME: room_name,
                        VarNames.TIME: get_milliseconds(),
                        VarNames.JS_MESSAGE_ID:
                        message[VarNames.JS_MESSAGE_ID],
                    }, RedisPrefix.generate_user(room_user.user_id))
        else:
            self.publish(
                {
                    VarNames.EVENT: Actions.SAVE_ROOM_SETTINGS,
                    VarNames.CHANNEL_ID: room.channel_id,
                    VarNames.CB_BY_SENDER: self.id,
                    VarNames.CHANNEL_CREATOR_ID: channel_creator_id,
                    VarNames.ROOM_CREATOR_ID: room.creator_id,
                    VarNames.HANDLER_NAME: HandlerNames.CHANNELS,
                    VarNames.CHANNEL_NAME: channel_name,
                    VarNames.ROOM_ID: room.id,
                    VarNames.VOLUME: message[VarNames.VOLUME],
                    VarNames.NOTIFICATIONS: message[VarNames.NOTIFICATIONS],
                    VarNames.P2P: message[VarNames.P2P],
                    VarNames.ROOM_NAME: room_name,
                    VarNames.TIME: get_milliseconds(),
                    VarNames.JS_MESSAGE_ID: message[VarNames.JS_MESSAGE_ID],
                }, self.channel)

    def get_users_channels_ids(self):
        channels_ids = Room.objects.filter(users__id=self.user_id,
                                           disabled=False).values_list(
                                               'channel_id', flat=True)
        return Channel.objects.filter(Q(id__in=channels_ids)
                                      | Q(creator=self.user_id),
                                      disabled=False).values_list('id',
                                                                  flat=True)

    def profile_save_settings(self, in_message):
        message = in_message[VarNames.CONTENT]
        UserProfile.objects.filter(id=self.user_id).update(
            suggestions=message[UserSettingsVarNames.SUGGESTIONS],
            embedded_youtube=message[UserSettingsVarNames.EMBEDDED_YOUTUBE],
            highlight_code=message[UserSettingsVarNames.HIGHLIGHT_CODE],
            message_sound=message[UserSettingsVarNames.MESSAGE_SOUND],
            incoming_file_call_sound=message[
                UserSettingsVarNames.INCOMING_FILE_CALL_SOUND],
            online_change_sound=message[
                UserSettingsVarNames.ONLINE_CHANGE_SOUND],
            logs=message[UserSettingsVarNames.LOGS],
            send_logs=message[UserSettingsVarNames.SEND_LOGS],
            theme=message[UserSettingsVarNames.THEME],
        )
        self.publish(
            self.set_settings(in_message[VarNames.JS_MESSAGE_ID], message),
            self.channel)

    def profile_save_user(self, in_message):
        message = in_message[VarNames.CONTENT]
        userprofile = UserProfile.objects.get(id=self.user_id)
        un = message[UserProfileVarNames.USERNAME]
        if userprofile.username != un:
            check_user(un)

        sex = message[UserProfileVarNames.SEX]
        UserProfile.objects.filter(id=self.user_id).update(
            username=un,
            name=message[UserProfileVarNames.NAME],
            city=message[UserProfileVarNames.CITY],
            surname=message[UserProfileVarNames.SURNAME],
            birthday=message[UserProfileVarNames.BIRTHDAY],
            contacts=message[UserProfileVarNames.CONTACTS],
            sex=settings.GENDERS_STR[sex],
        )
        self.publish(
            self.set_user_profile(in_message[VarNames.JS_MESSAGE_ID], message),
            self.channel)
        if userprofile.sex_str != sex or userprofile.username != un:
            self.publish(self.changed_user_profile(sex, self.user_id, un),
                         settings.ALL_ROOM_ID)

    def profile_save_image(self, request):
        pass
        # UserProfile.objects.filter(id=request.user.id).update(
        # 	suggestions=request.POST['suggestions'],
        # 	embedded_youtube=request.POST['embedded_youtube'],
        # 	highlight_code=request.POST['highlight_code'],
        # 	message_sound=request.POST['message_sound'],
        # 	incoming_file_call_sound=request.POST['incoming_file_call_sound'],
        # 	online_change_sound=request.POST['online_change_sound'],
        # 	logs=request.POST['logs'],
        # 	send_logs=request.POST['send_logs'],
        # 	theme=request.POST['theme'],
        # )
        # return HttpResponse(settings.VALIDATION_IS_OK, content_type='text/plain')

    def invite_user(self, message):
        room_id = message[VarNames.ROOM_ID]
        if room_id not in self.channels:
            raise ValidationError(
                "Access denied, only allowed for channels {}".format(
                    self.channels))
        room = Room.objects.get(id=room_id)
        if room.is_private:
            raise ValidationError(
                "You can't add users to direct room, create a new room instead"
            )
        users = message.get(VarNames.ROOM_USERS)
        users_in_room = list(
            RoomUsers.objects.filter(room_id=room_id).values_list('user_id',
                                                                  flat=True))
        intersect = set(users_in_room) & set(users)
        if bool(intersect):
            raise ValidationError("Users %s are already in the room",
                                  intersect)
        users_in_room.extend(users)

        max_id = Message.objects.filter(room_id=room_id).aggregate(
            Max('id'))['id__max']
        if not max_id:
            max_id = Message.objects.all().aggregate(Max('id'))['id__max']
        ru = [
            RoomUsers(user_id=user_id,
                      room_id=room_id,
                      last_read_message_id=max_id,
                      volume=1,
                      notifications=False) for user_id in users
        ]
        RoomUsers.objects.bulk_create(ru)

        add_invitee = {
            VarNames.EVENT: Actions.ADD_INVITE,
            VarNames.ROOM_ID: room_id,
            VarNames.ROOM_USERS: users_in_room,
            VarNames.ROOM_NAME: room.name,
            VarNames.INVITEE_USER_ID: users,
            VarNames.INVITER_USER_ID: self.user_id,
            VarNames.HANDLER_NAME: HandlerNames.CHANNELS,
            VarNames.TIME: get_milliseconds(),
            VarNames.VOLUME: 1,
            VarNames.NOTIFICATIONS: False,
        }
        add_invitee_dumped = encode_message(add_invitee, True)
        for user in users:
            self.raw_publish(add_invitee_dumped,
                             RedisPrefix.generate_user(user))

        invite = {
            VarNames.EVENT: Actions.INVITE_USER,
            VarNames.ROOM_ID: room_id,
            VarNames.INVITEE_USER_ID: users,
            VarNames.INVITER_USER_ID: self.user_id,
            VarNames.HANDLER_NAME: HandlerNames.CHANNELS,
            VarNames.ROOM_USERS: users_in_room,
            VarNames.TIME: get_milliseconds(),
            VarNames.CB_BY_SENDER: self.id,
            VarNames.JS_MESSAGE_ID: message[VarNames.JS_MESSAGE_ID]
        }
        self.publish(invite, room_id, True)

    def respond_ping(self, message):
        self.ws_write(self.responde_pong(message[VarNames.JS_MESSAGE_ID]))

    def process_pong_message(self, message):
        self.last_client_ping = message[VarNames.TIME]

    def process_ping_message(self, message):
        def call_check():
            if message[VarNames.TIME] != self.last_client_ping:
                self.close(408, "Ping timeout")

        IOLoop.instance().call_later(settings.PING_CLOSE_SERVER_DELAY,
                                     call_check)

    def delete_channel(self, message):
        channel_id = message[VarNames.CHANNEL_ID]
        channel = Channel.objects.get(id=channel_id)
        if channel.creator_id != self.user_id:
            raise ValidationError(
                f"Only admin can delete this channel. Please ask ${User.objects.get(id=channel.creator_id).username}"
            )
        # if Room.objects.filter(channel_id=channel_id).count() > 0:
        users_id = list(
            RoomUsers.objects.filter(room__channel_id=channel_id).values_list(
                'user_id', flat=True))
        if len(users_id) > 0:
            raise ValidationError(
                f"Some users are still in the rooms on this channel Please ask them to leave"
            )
        Room.objects.filter(channel_id=channel_id).update(disabled=True)
        channel.disabled = True
        channel.save()

        message = {
            VarNames.EVENT: Actions.DELETE_CHANNEL,
            VarNames.CHANNEL_ID: channel_id,
            VarNames.HANDLER_NAME: HandlerNames.CHANNELS,
            VarNames.TIME: get_milliseconds(),
            VarNames.CB_BY_SENDER: self.id,
            VarNames.JS_MESSAGE_ID: message[VarNames.JS_MESSAGE_ID]
        }
        self.publish(message, self.channel)

    def delete_room(self, message):
        room_id = message[VarNames.ROOM_ID]
        js_id = message[VarNames.JS_MESSAGE_ID]
        if room_id not in self.channels or room_id == ALL_ROOM_ID:
            raise ValidationError('You are not allowed to exit this room')
        room = Room.objects.get(id=room_id)
        if room.disabled:
            raise ValidationError('Room is already deleted')
        if room.name is None:  # if private then disable
            room.disabled = True
            room.save()
        else:  # if public -> leave the room, delete the link
            RoomUsers.objects.filter(room_id=room.id,
                                     user_id=self.user_id).delete()
        ru = list(
            RoomUsers.objects.filter(room_id=room.id).values_list('user_id',
                                                                  flat=True))
        message = self.unsubscribe_direct_message(room_id, js_id, self.id, ru,
                                                  room.name)
        self.publish(message, room_id, True)

    def edit_message(self, data):
        message = Message.objects.get(id=data[VarNames.MESSAGE_ID])
        validate_edit_message(self.user_id, message)
        message.content = data[VarNames.CONTENT]
        MessageHistory(message=message,
                       content=message.content,
                       giphy=message.giphy).save()
        message.edited_times += 1
        giphy_match = self.isGiphy(data[VarNames.CONTENT])
        if message.content is None:
            Message.objects.filter(id=data[VarNames.MESSAGE_ID]).update(
                deleted=True, edited_times=message.edited_times, content=None)
            self.publish(
                self.create_send_message(message, Actions.DELETE_MESSAGE,
                                         None), message.room_id)
        elif giphy_match is not None:
            self.edit_message_giphy(giphy_match, message)
        else:
            self.edit_message_edit(data, message)

    def edit_message_giphy(self, giphy_match, message):
        def edit_glyphy(message, giphy):
            Message.objects.filter(id=message.id).update(
                content=message.content,
                symbol=message.symbol,
                giphy=giphy,
                edited_times=message.edited_times)
            message.giphy = giphy
            self.publish(
                self.create_send_message(message, Actions.EDIT_MESSAGE, None),
                message.room_id)

        self.search_giphy(message, giphy_match, edit_glyphy)

    def edit_message_edit(self, data, message):
        action = Actions.EDIT_MESSAGE
        message.giphy = None
        files = UploadedFile.objects.filter(id__in=data.get(VarNames.FILES),
                                            user_id=self.user_id)
        if files:
            update_symbols(files, message)
            up_files_to_img(files, message.id)
        if message.symbol:  # fetch all, including that we just store
            db_images = Image.objects.filter(message_id=message.id)
            prep_files = MessagesCreator.prepare_img_video(
                db_images, message.id)
        else:
            prep_files = None
        Message.objects.filter(id=message.id).update(
            content=message.content,
            symbol=message.symbol,
            giphy=None,
            edited_times=message.edited_times)
        self.publish(self.create_send_message(message, action, prep_files),
                     message.room_id)

    def send_client_new_channel(self, message):
        room_id = message[VarNames.ROOM_ID]
        self.add_channel(room_id)

    def send_client_delete_channel(self, message):
        room_id = message[VarNames.ROOM_ID]
        if message[VarNames.USER_ID] == self.user_id or message[
                VarNames.ROOM_NAME] is None:
            self.async_redis.unsubscribe((room_id, ))
            self.channels.remove(room_id)
            channels = {
                VarNames.EVENT: Actions.DELETE_MY_ROOM,
                VarNames.ROOM_ID: room_id,
                VarNames.HANDLER_NAME: HandlerNames.CHANNELS,
                VarNames.JS_MESSAGE_ID: message[VarNames.JS_MESSAGE_ID],
            }
            self.ws_write(channels)
        else:
            self.ws_write({
                VarNames.EVENT: Actions.USER_LEAVES_ROOM,
                VarNames.ROOM_ID: room_id,
                VarNames.USER_ID: message[VarNames.USER_ID],
                VarNames.ROOM_USERS: message[VarNames.ROOM_USERS],
                VarNames.HANDLER_NAME: HandlerNames.CHANNELS
            })
        return True

    def process_get_messages(self, data):
        """
		:type data: dict
		"""
        header_id = data.get(VarNames.GET_MESSAGES_HEADER_ID, None)
        count = int(data.get(VarNames.GET_MESSAGES_COUNT, 10))
        room_id = data[VarNames.ROOM_ID]
        self.logger.info('!! Fetching %d messages starting from %s', count,
                         header_id)
        if header_id is None:
            messages = Message.objects.filter(
                room_id=room_id).order_by('-pk')[:count]
        else:
            messages = Message.objects.filter(
                Q(id__lt=header_id),
                Q(room_id=room_id)).order_by('-pk')[:count]
        imv = get_message_images_videos(messages)
        response = self.get_messages(messages, room_id, imv,
                                     MessagesCreator.prepare_img_video,
                                     data[VarNames.JS_MESSAGE_ID])
        self.ws_write(response)
Example #30
0
class ChatHandler(WebSocketHandler):

    def check_origin(self, origin):
        return True

    def open(self, chat_url):
        sockets.add(self)
        print chat_url
        self.user = get_user(self.cookies)
        if self.user is None:
            self.close()
            return
        self.chat = get_chat(chat_url)
        if self.chat is None:
            self.close()
            return
        self.chat_user = get_chat_user(self.chat.id, self.user.id)
        if self.chat_user is None:
            self.close()
            return
        self.socket_id = str(uuid4())
        # Fire online message, but only if this is the only tab we have open.
        online_symbols = set(int(_) for _ in publish_client.hvals("online:"+str(self.chat.id)))
        if self.chat_user.symbol not in online_symbols:
            publish_client.publish("chat:"+str(self.chat.id), json.dumps({
                "action": "online",
                "symbol": symbols[self.chat_user.symbol],
            }))
        # See if the other person is online.
        for symbol in online_symbols:
            if symbol == self.chat_user.symbol:
                continue
            self.write_message({
                "action": "online",
                "symbol": symbols[symbol],
            })
        publish_client.hset("online:"+str(self.chat.id), self.socket_id, self.chat_user.symbol)
        self.redis_listen()
        self.ignore_next_message = False
        # Send the backlog if necessary.
        if "after" in self.request.query_arguments:
            print "after"
            try:
                after = int(self.request.query_arguments["after"][0])
            except ValueError:
                return
            Session = sm()
            for message in Session.query(Message).filter(and_(
                Message.chat_id == self.chat.id,
                Message.id > after,
            )):
                print message
                self.write_message({
                    "action": "message",
                    "message": {
                        "id": message.id,
                        "type": message.type,
                        "colour": message.colour,
                        "symbol": symbols[message.symbol],
                        "text": message.text,
                    }
                })
            Session.commit()

    def on_message(self, message_string):
        message = json.loads(message_string)
        if message["action"] in ("typing", "stopped_typing"):
            publish_client.publish("chat:"+str(self.chat.id), json.dumps({
                "action": message["action"],
                "symbol": symbols[self.chat_user.symbol],
            }))
            # Ignore our own typing messages.
            self.ignore_next_message = True
        print message

    def on_close(self):
        # Unsubscribe here and let the exit callback handle disconnecting.
        self.redis_client.unsubscribe("chat:"+str(self.chat.id))
        publish_client.hdel("online:"+str(self.chat.id), self.socket_id)
        # Fire offline message, but only if we don't have any other tabs open.
        if str(self.chat_user.symbol) not in publish_client.hvals("online:"+str(self.chat.id)):
            publish_client.publish("chat:"+str(self.chat.id), json.dumps({
                "action": "offline",
                "symbol": symbols[self.chat_user.symbol],
            }))
        sockets.remove(self)

    @engine
    def redis_listen(self):
        self.redis_client = Client(unix_socket_path=config.get("app:main", "cherubplay.socket_pubsub"))
        yield Task(self.redis_client.subscribe, "chat:"+str(self.chat.id))
        self.redis_client.listen(self.on_redis_message, self.on_redis_unsubscribe)

    def on_redis_message(self, message):
        if message.kind=="message":
            if not self.ignore_next_message:
                self.write_message(message.body)
                print "redis message:", message.body
            else:
                self.ignore_next_message = False

    def on_redis_unsubscribe(self, callback):
        self.redis_client.disconnect()
Example #31
0
 def __init__(self, *args, **kwargs):
     super(MessageHandler, self).__init__(*args, **kwargs)
     self.redis = Client()
     self.redis.connect()
Example #32
0
class RedisSubscriber:
    _instance = None
    _lock = Lock()

    def __init__(self):
        worker_logger.info('Initialized Redis Subscriber instance')
        self._client = Client(host=REDIS_HOST, port=int(REDIS_PORT))
        self._subscribers = {}
        self._listen()

    def __del__(self):
        worker_logger.info('Redis Subscriber was successfully destroyed')

    @classmethod
    def instance(cls):
        with cls._lock:
            if cls._instance is None:
                cls._instance = RedisSubscriber()
        return cls._instance

    def subscribe(self, channel_key, callback):
        worker_logger.info(
            'Subscribing channel - %s , with the callback - %s' %
            (channel_key, callback.__name__))
        self._subscribers.update({channel_key: callback})

    def unsubscribe(self, channel_key):
        worker_logger.info("Unsubscribing from channel - %s" % channel_key)
        if channel_key in self._subscribers:
            del self._subscribers[channel_key]

    @tornado.gen.engine
    def _listen(self):
        self._client.connect()
        yield tornado.gen.Task(
            self._client.psubscribe,
            "__keyspace*:%s" % (REDIS_GENERAL_CHANNEL % '*'))
        self._client.listen(self._message_handler)

    def _message_handler(self, message):
        if message.kind == 'pmessage':
            channel_key = re.search(REDIS_GENERAL_CHANNEL % '(.*)',
                                    message.channel)
            channel_callbacks = []
            if channel_key is not None:
                channel_key = channel_key.group(0)
                if channel_key in self._subscribers:
                    channel_callbacks[0] = self._subscribers.get(channel_key)
                else:
                    for subscriber_channel_key in self._subscribers.keys():
                        re_channel_key = subscriber_channel_key.replace(
                            '*', '.*')
                        if re.match(re_channel_key, channel_key):
                            channel_callbacks.append(
                                self._subscribers.get(subscriber_channel_key))
            if len(channel_callbacks):
                for channel_callback in channel_callbacks:
                    try:
                        tornado.ioloop.IOLoop.instance().call_later(
                            callback=channel_callback,
                            delay=0,
                            message=message)
                    except Exception as e:
                        worker_logger.warn('Failed to run channel callback')
                        worker_logger.exception(e)