From 40400869d0bdb0d6d028df0b2a8ba1f664c9be1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Evren=20Esat=20=C3=96zkan?= Date: Tue, 28 Jun 2016 03:03:48 +0300 Subject: [PATCH] rref #5367 rref #5366 ref zetaops/zengine#66 ref zetaops/zengine#65 --- zengine/messaging/lib.py | 21 ++++++++ zengine/messaging/model.py | 49 +++++++++++++++-- zengine/messaging/views.py | 71 ++++++++++++++++++------- zengine/settings.py | 3 +- zengine/tornado_server/queue_manager.py | 7 +++ zengine/views/auth.py | 4 ++ 6 files changed, 130 insertions(+), 25 deletions(-) diff --git a/zengine/messaging/lib.py b/zengine/messaging/lib.py index ac62a893..597ff0e7 100644 --- a/zengine/messaging/lib.py +++ b/zengine/messaging/lib.py @@ -13,8 +13,21 @@ from pyoko.conf import settings from zengine.client_queue import BLOCKING_MQ_PARAMS +from zengine.lib.cache import Cache +class ConnectionStatus(Cache): + """ + Cache object for workflow instances. + + Args: + wf_token: Token of the workflow instance. + """ + PREFIX = 'ONOFF' + + def __init__(self, user_id): + super(ConnectionStatus, self).__init__(user_id) + class BaseUser(object): connection = None @@ -48,6 +61,14 @@ def set_password(self, raw_password): self.password = pbkdf2_sha512.encrypt(raw_password, rounds=10000, salt_size=10) + def is_online(self, status=None): + if status is None: + return ConnectionStatus(self.key).get() + ConnectionStatus(self.key).set(status) + if status == False: + pass + # TODO: do + def pre_save(self): """ encrypt password if not already encrypted """ if self.password and not self.password.startswith('$pbkdf2'): diff --git a/zengine/messaging/model.py b/zengine/messaging/model.py index aa6b9e06..1bb36a99 100644 --- a/zengine/messaging/model.py +++ b/zengine/messaging/model.py @@ -84,8 +84,8 @@ def get_or_create_direct_channel(cls, initiator, receiver): else: channel_name = '%s_%s' % (initiator.key, receiver.key) channel = cls(is_direct=True, code_name=channel_name).save() - Subscription(channel=channel, user=initiator).save() - Subscription(channel=channel, user=receiver).save() + Subscriber(channel=channel, user=initiator).save() + Subscriber(channel=channel, user=receiver).save() return channel def add_message(self, body, title=None, sender=None, url=None, typ=2, receiver=None): @@ -95,6 +95,10 @@ def add_message(self, body, title=None, sender=None, url=None, typ=2, receiver=N return Message(sender=sender, body=body, msg_title=title, url=url, typ=typ, channel=self, receiver=receiver).save() + def get_last_messages(self): + # TODO: Refactor this with RabbitMQ Last Cached Messages exchange + return self.message_set.objects.filter()[:20] + @classmethod def _connect_mq(cls): if cls.connection is None or cls.connection.is_closed: @@ -123,13 +127,13 @@ def post_creation(self): self.create_exchange() -class Subscription(Model): +class Subscriber(Model): """ Permission model """ channel = Channel() - user = UserModel(reverse_name='channels') + user = UserModel(reverse_name='subscriptions') is_muted = field.Boolean("Mute the channel") inform_me = field.Boolean("Inform when I'm mentioned", default=True) visible = field.Boolean("Show under user's channel list", default=True) @@ -143,6 +147,10 @@ def _connect_mq(cls): cls.connection, cls.channel = get_mq_connection() return cls.channel + def unread_count(self): + # FIXME: track and return actual unread message count + return 0 + def create_exchange(self): """ Creates user's private exchange @@ -200,6 +208,32 @@ class Message(Model): body = field.String("Body") url = field.String("URL") + def get_actions_for(self, user): + actions = [ + ('Favorite', 'favorite_message') + ] + if self.sender == user: + actions.extend([ + ('Delete', 'delete_message'), + ('Edit', 'delete_message') + ]) + else: + actions.extend([ + ('Flag', 'flag_message') + ]) + + def serialize_for(self, user): + return { + 'content': self.body, + 'type': self.typ, + 'attachments': [attachment.serialize() for attachment in self.attachment_set], + 'title': self.msg_title, + 'sender_name': self.sender.full_name, + 'sender_key': self.sender.key, + 'key': self.key, + 'actions': self.get_actions_for(user), + } + def __unicode__(self): content = self.msg_title or self.body return "%s%s" % (content[:30], '...' if len(content) > 30 else '') @@ -225,6 +259,13 @@ class Attachment(Model): channel = Channel() message = Message() + def serialize(self): + return { + 'description': self.description, + 'file_name': self.name, + 'url': "%s%s" % (settings.S3_PUBLIC_URL, self.file) + } + def __unicode__(self): return self.name diff --git a/zengine/messaging/views.py b/zengine/messaging/views.py index 63c582f9..07bbfb25 100644 --- a/zengine/messaging/views.py +++ b/zengine/messaging/views.py @@ -14,20 +14,18 @@ UserModel = get_object_from_path(settings.USER_MODEL) - def create_message(current): """ Creates a message for the given channel. .. code-block:: python - # request: + # request: { 'view':'_zops_create_message', 'message': { 'channel': "code_name of the channel", - 'receiver': "Key of receiver. Can be blank for non-direct messages", - 'client_id': "Client side unique id for referencing this message", + 'receiver': key, " of receiver. Should be set only for direct messages", 'title': "Title of the message. Can be blank.", 'body': "Message body.", 'type': zengine.messaging.model.MSG_TYPES, @@ -38,7 +36,7 @@ def create_message(current): }]} # response: { - 'msg_key': "Key of the just created message object", + 'msg_key': key, # of the just created message object, } """ @@ -53,14 +51,16 @@ def create_message(current): Attachment(channel=ch, msg=msg_obj, name=atch['name'], file=atch['content'], description=atch['description'], typ=typ).save() -def _dedect_file_type(current, name, content): - # TODO: Attachment type detection + +def _dedect_file_type(name, content): + # FIXME: Implement attachment type detection return 1 # Return as Document for now -def show_public_channel(current): + +def show_channel(current): """ Initial display of channel content. - Returns chanel description, no of members, last 20 messages etc. + Returns channel description, members, no of members, last 20 messages etc. .. code-block:: python @@ -68,12 +68,12 @@ def show_public_channel(current): # request: { 'view':'_zops_show_public_channel', - 'channel_key': "Key of the requested channel" + 'channel_key': key, } # response: { - 'channel_key': "key of channel", + 'channel_key': key, 'description': string, 'no_of_members': int, 'member_list': [ @@ -83,25 +83,45 @@ def show_public_channel(current): }], 'last_messages': [ {'content': string, - 'key': string, - 'actions':[ - {'title': string, - 'cmd': string - } - ] + 'title': string, + 'channel_key': key, + 'sender_name': string, + 'sender_key': key, + 'type': int, + 'key': key, + 'actions':[('name_string', 'cmd_string'),] } ] } """ - - + ch_key = current.input['channel_key'] + ch = Channel.objects.get(ch_key) + current.output = {'channel_key': ch_key, + 'description': ch.description, + 'no_of_members': len(ch.subscriber_set), + 'member_list': [{'name': sb.user.full_name, + 'is_online': sb.user.is_online(), + 'avatar_url': sb.user.get_avatar_url() + } for sb in ch.subscriber_set], + 'last_messages': [msg.serialize_for(current.user) + for msg in ch.get_last_messages()] + } + +def mark_offline_user(current): + current.user.is_online(False) def list_channels(current): - pass + return [ + {'name': sbs.channel.name, + 'key': sbs.channel.key, + 'unread': sbs.unread_count()} for sbs in + current.user.subscriptions] + def create_public_channel(current): pass + def create_direct_channel(current): """ Create a One-To-One channel for current user and selected user. @@ -109,11 +129,22 @@ def create_direct_channel(current): """ pass + +def create_broadcast_channel(current): + """ + Create a One-To-One channel for current user and selected user. + + """ + pass + + def find_message(current): pass + def delete_message(current): pass + def edit_message(current): pass diff --git a/zengine/settings.py b/zengine/settings.py index 3c6fa042..c3d0cf0f 100644 --- a/zengine/settings.py +++ b/zengine/settings.py @@ -116,7 +116,8 @@ 'dashboard': 'zengine.views.menu.Menu', 'ping': 'zengine.views.dev_utils.Ping', '_zops_create_message': 'zengine.messaging.views.create_message', - + 'mark_offline_user': 'zengine.messaging.views.mark_offline_user', + 'show_channel': 'zengine.messaging.views.show_channel', } if DEBUG: diff --git a/zengine/tornado_server/queue_manager.py b/zengine/tornado_server/queue_manager.py index a35427d3..d06b8878 100644 --- a/zengine/tornado_server/queue_manager.py +++ b/zengine/tornado_server/queue_manager.py @@ -186,8 +186,15 @@ def register_websocket(self, sess_id, ws): self.websockets[sess_id] = ws channel = self.create_out_channel(sess_id) + def inform_disconnection(self, sess_id): + self.websockets[sess_id].write_message({ + 'view': 'mark_offline_user', + 'sess_id': sess_id + }) + def unregister_websocket(self, sess_id): try: + self.inform_disconnection(sess_id) del self.websockets[sess_id] except KeyError: log.exception("Non-existent websocket") diff --git a/zengine/views/auth.py b/zengine/views/auth.py index 8afa7ce1..cc3dfc0f 100644 --- a/zengine/views/auth.py +++ b/zengine/views/auth.py @@ -64,6 +64,9 @@ def _do_binding(self): # routing_key="#" ) + def _user_is_online(self): + self.current.user.is_online(True) + def do_view(self): """ Authenticate user with given credentials. @@ -79,6 +82,7 @@ def do_view(self): self.current.input['password']) self.current.task_data['login_successful'] = auth_result if auth_result: + self._user_is_online() self._do_binding() user_sess = UserSessionID(self.current.user_id) old_sess_id = user_sess.get()