mirror of
				https://github.com/zulip/zulip.git
				synced 2025-11-04 14:03:30 +00:00 
			
		
		
		
	[manual] Remove /messages/latest API and related legacy code.
This requires doing a puppet apply on our servers to take effect properly. (imported from commit 19dc56f071f07a5d2571eef49dd835121b2e82b6)
This commit is contained in:
		@@ -14,7 +14,7 @@ location /static/ {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# Send longpoll requests to Tornado
 | 
			
		||||
location ~ /json/get_updates|/api/v1/get_messages|/api/v1/messages/latest|/json/get_events|/api/v1/events {
 | 
			
		||||
location ~ /json/get_events|/api/v1/events {
 | 
			
		||||
    proxy_pass       http://localhost:9993;
 | 
			
		||||
    proxy_redirect   off;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -83,8 +83,7 @@ class Resource(resource.Resource):
 | 
			
		||||
    def getChild(self, name, request):
 | 
			
		||||
        request.requestHeaders.setRawHeaders('X-Forwarded-Host', [proxy_host])
 | 
			
		||||
 | 
			
		||||
        if (request.uri in ['/json/get_updates', '/api/v1/get_messages', '/json/get_events'] or
 | 
			
		||||
            request.uri.startswith('/api/v1/messages/latest') or
 | 
			
		||||
        if (request.uri in ['/json/get_events'] or
 | 
			
		||||
            request.uri.startswith('/api/v1/events') or
 | 
			
		||||
            request.uri.startswith('/sockjs')):
 | 
			
		||||
            return proxy.ReverseProxyResource('localhost', tornado_port, '/'+name)
 | 
			
		||||
 
 | 
			
		||||
@@ -1495,7 +1495,7 @@ def do_update_message(user_profile, message_id, subject, propagate_mode, content
 | 
			
		||||
                                "rendered_content_version", "last_edit_time",
 | 
			
		||||
                                "edit_history"])
 | 
			
		||||
 | 
			
		||||
    # Update the message as stored in both the (deprecated) message
 | 
			
		||||
    # Update the message as stored in the (deprecated) message
 | 
			
		||||
    # cache (for shunting the message over to Tornado in the old
 | 
			
		||||
    # get_messages API) and also the to_dict caches.
 | 
			
		||||
    items_for_memcached = {}
 | 
			
		||||
 
 | 
			
		||||
@@ -86,10 +86,7 @@ class Command(BaseCommand):
 | 
			
		||||
                queue_client.register_json_consumer('tornado_return', respond_send_message)
 | 
			
		||||
 | 
			
		||||
            try:
 | 
			
		||||
                urls = (r"/json/get_updates",
 | 
			
		||||
                        r"/api/v1/get_messages",
 | 
			
		||||
                        r"/notify_tornado",
 | 
			
		||||
                        r"/api/v1/messages/latest",
 | 
			
		||||
                urls = (r"/notify_tornado",
 | 
			
		||||
                        r"/json/get_events",
 | 
			
		||||
                        r"/api/v1/events",
 | 
			
		||||
                        )
 | 
			
		||||
 
 | 
			
		||||
@@ -13,7 +13,6 @@ from zerver.models import Message, UserProfile, Stream, Recipient, Subscription,
 | 
			
		||||
    get_display_recipient, Realm, Client, UserActivity, \
 | 
			
		||||
    PreregistrationUser, UserMessage, \
 | 
			
		||||
    get_user_profile_by_email, email_to_domain, get_realm, get_stream, get_client
 | 
			
		||||
from zerver.tornadoviews import json_get_updates, api_get_messages
 | 
			
		||||
from zerver.decorator import RespondAsynchronously, \
 | 
			
		||||
    RequestVariableConversionError, profiled, JsonableError
 | 
			
		||||
from zerver.lib.initial_password import initial_password
 | 
			
		||||
@@ -2421,66 +2420,6 @@ class POSTRequestMock(object):
 | 
			
		||||
        self.session = DummySession()
 | 
			
		||||
        self.META = {'PATH_INFO': 'test'}
 | 
			
		||||
 | 
			
		||||
class GetUpdatesTest(AuthedTestCase):
 | 
			
		||||
 | 
			
		||||
    def common_test_get_updates(self, view_func, extra_post_data = {}):
 | 
			
		||||
        user_profile = get_user_profile_by_email("hamlet@zulip.com")
 | 
			
		||||
        message_content = 'tornado test message'
 | 
			
		||||
        self.got_callback = False
 | 
			
		||||
 | 
			
		||||
        def callback(response):
 | 
			
		||||
            self.got_callback = True
 | 
			
		||||
            msg = response['messages'][0]
 | 
			
		||||
            if str(msg['content_type']) == 'text/html':
 | 
			
		||||
                self.assertEqual('<p>%s</p>' % message_content, msg['content'])
 | 
			
		||||
            else:
 | 
			
		||||
                self.assertEqual(message_content, msg['content'])
 | 
			
		||||
 | 
			
		||||
        post_data = {}
 | 
			
		||||
        post_data.update(extra_post_data)
 | 
			
		||||
        request = POSTRequestMock(post_data, user_profile, callback)
 | 
			
		||||
        self.assertEqual(view_func(request), RespondAsynchronously)
 | 
			
		||||
        self.send_message("hamlet@zulip.com", "hamlet@zulip.com",
 | 
			
		||||
                Recipient.PERSONAL, message_content)
 | 
			
		||||
        self.assertTrue(self.got_callback)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def test_json_get_updates(self):
 | 
			
		||||
        """
 | 
			
		||||
        json_get_updates returns messages with IDs greater than the
 | 
			
		||||
        last_received ID.
 | 
			
		||||
        """
 | 
			
		||||
        self.login("hamlet@zulip.com")
 | 
			
		||||
        self.common_test_get_updates(json_get_updates)
 | 
			
		||||
 | 
			
		||||
    def test_api_get_messages(self):
 | 
			
		||||
        """
 | 
			
		||||
        Same as above, but for the API view
 | 
			
		||||
        """
 | 
			
		||||
        email = "hamlet@zulip.com"
 | 
			
		||||
        api_key = self.get_api_key(email)
 | 
			
		||||
        self.common_test_get_updates(api_get_messages, {'email': email, 'api-key': api_key})
 | 
			
		||||
 | 
			
		||||
    def test_missing_last_received(self):
 | 
			
		||||
        """
 | 
			
		||||
        Calling json_get_updates without any arguments should work
 | 
			
		||||
        """
 | 
			
		||||
        self.login("hamlet@zulip.com")
 | 
			
		||||
        user_profile = get_user_profile_by_email("hamlet@zulip.com")
 | 
			
		||||
 | 
			
		||||
        request = POSTRequestMock({}, user_profile)
 | 
			
		||||
        self.assertEqual(json_get_updates(request), RespondAsynchronously)
 | 
			
		||||
 | 
			
		||||
    def test_bad_input(self):
 | 
			
		||||
        """
 | 
			
		||||
        Specifying a bad value for 'pointer' should return an error
 | 
			
		||||
        """
 | 
			
		||||
        self.login("hamlet@zulip.com")
 | 
			
		||||
        user_profile = get_user_profile_by_email("hamlet@zulip.com")
 | 
			
		||||
 | 
			
		||||
        request = POSTRequestMock({'pointer': 'foo'}, user_profile)
 | 
			
		||||
        self.assertRaises(RequestVariableConversionError, json_get_updates, request)
 | 
			
		||||
 | 
			
		||||
class GetProfileTest(AuthedTestCase):
 | 
			
		||||
 | 
			
		||||
    def common_update_pointer(self, email, pointer):
 | 
			
		||||
 
 | 
			
		||||
@@ -3,8 +3,8 @@ from __future__ import absolute_import
 | 
			
		||||
from django.conf import settings
 | 
			
		||||
from django.utils.timezone import now
 | 
			
		||||
 | 
			
		||||
from zerver.models import Message, UserProfile, UserMessage, \
 | 
			
		||||
    Recipient, Stream, get_stream, get_user_profile_by_id
 | 
			
		||||
from zerver.models import Message, UserProfile, \
 | 
			
		||||
    Recipient, get_user_profile_by_id
 | 
			
		||||
 | 
			
		||||
from zerver.decorator import JsonableError
 | 
			
		||||
from zerver.lib.cache import cache_get_many, message_cache_key, \
 | 
			
		||||
@@ -15,223 +15,17 @@ from zerver.lib.event_queue import get_client_descriptors_for_user,\
 | 
			
		||||
    get_client_descriptors_for_realm_all_streams
 | 
			
		||||
from zerver.lib.timestamp import timestamp_to_datetime
 | 
			
		||||
 | 
			
		||||
import os
 | 
			
		||||
import sys
 | 
			
		||||
import time
 | 
			
		||||
import logging
 | 
			
		||||
import requests
 | 
			
		||||
import ujson
 | 
			
		||||
import subprocess
 | 
			
		||||
import collections
 | 
			
		||||
import datetime
 | 
			
		||||
from django.db import connection
 | 
			
		||||
 | 
			
		||||
# Send email notifications to idle users
 | 
			
		||||
# after they are idle for 1 hour
 | 
			
		||||
NOTIFY_AFTER_IDLE_HOURS = 1
 | 
			
		||||
 | 
			
		||||
class Callbacks(object):
 | 
			
		||||
    # A user received a message. The key is user_profile.id.
 | 
			
		||||
    TYPE_USER_RECEIVE = 0
 | 
			
		||||
 | 
			
		||||
    # A stream received a message. The key is a tuple
 | 
			
		||||
    #   (realm_id, lowercased stream name).
 | 
			
		||||
    # See comment attached to the global stream_messages for why.
 | 
			
		||||
    # Callers of this callback need to be careful to provide
 | 
			
		||||
    # a lowercased stream name.
 | 
			
		||||
    TYPE_STREAM_RECEIVE = 1
 | 
			
		||||
 | 
			
		||||
    # A user's pointer was updated. The key is user_profile.id.
 | 
			
		||||
    TYPE_POINTER_UPDATE = 2
 | 
			
		||||
 | 
			
		||||
    TYPE_MAX = 3
 | 
			
		||||
 | 
			
		||||
    def __init__(self):
 | 
			
		||||
        self.table = {}
 | 
			
		||||
 | 
			
		||||
    def add(self, key, cb_type, callback):
 | 
			
		||||
        if not self.table.has_key(key):
 | 
			
		||||
            self.create_key(key)
 | 
			
		||||
        self.table[key][cb_type].append(callback)
 | 
			
		||||
 | 
			
		||||
    def call(self, key, cb_type, **kwargs):
 | 
			
		||||
        if not self.table.has_key(key):
 | 
			
		||||
            self.create_key(key)
 | 
			
		||||
 | 
			
		||||
        for cb in self.table[key][cb_type]:
 | 
			
		||||
            cb(**kwargs)
 | 
			
		||||
 | 
			
		||||
        self.table[key][cb_type] = []
 | 
			
		||||
 | 
			
		||||
    def create_key(self, key):
 | 
			
		||||
        self.table[key] = [[] for i in range(0, Callbacks.TYPE_MAX)]
 | 
			
		||||
 | 
			
		||||
callbacks_table = Callbacks()
 | 
			
		||||
 | 
			
		||||
def add_user_receive_callback(user_profile, cb):
 | 
			
		||||
    callbacks_table.add(user_profile.id, Callbacks.TYPE_USER_RECEIVE, cb)
 | 
			
		||||
 | 
			
		||||
def add_stream_receive_callback(realm_id, stream_name, cb):
 | 
			
		||||
    callbacks_table.add((realm_id, stream_name.lower()), Callbacks.TYPE_STREAM_RECEIVE, cb)
 | 
			
		||||
 | 
			
		||||
def add_pointer_update_callback(user_profile, cb):
 | 
			
		||||
    callbacks_table.add(user_profile.id, Callbacks.TYPE_POINTER_UPDATE, cb)
 | 
			
		||||
 | 
			
		||||
# in-process caching mechanism for tracking usermessages
 | 
			
		||||
#
 | 
			
		||||
# user table:   Map user_profile_id => [deque of message ids he received]
 | 
			
		||||
#
 | 
			
		||||
# We don't use all the features of a deque -- the important ones are:
 | 
			
		||||
# * O(1) insert of new highest message id
 | 
			
		||||
# * O(k) read of highest k message ids
 | 
			
		||||
# * Automatic maximum size support.
 | 
			
		||||
#
 | 
			
		||||
# stream table: Map (realm_id, lowercased stream name) => [deque of message ids it received]
 | 
			
		||||
#
 | 
			
		||||
# Why don't we index by the stream_id? Because the client will make a
 | 
			
		||||
# request that specifies a particular realm and stream name, and since
 | 
			
		||||
# we're running within tornado, we don't want to have to do a database
 | 
			
		||||
# lookup to find the matching entry in this table.
 | 
			
		||||
 | 
			
		||||
mtables = {
 | 
			
		||||
    'user': {},
 | 
			
		||||
    'stream': {},
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
USERMESSAGE_CACHE_COUNT = 25000
 | 
			
		||||
STREAMMESSAGE_CACHE_COUNT = 5000
 | 
			
		||||
cache_minimum_id = sys.maxint
 | 
			
		||||
def initialize_user_messages():
 | 
			
		||||
    global cache_minimum_id
 | 
			
		||||
    try:
 | 
			
		||||
        cache_minimum_id = Message.objects.all().order_by("-id")[0].id - USERMESSAGE_CACHE_COUNT
 | 
			
		||||
    except Message.DoesNotExist:
 | 
			
		||||
        cache_minimum_id = 1
 | 
			
		||||
 | 
			
		||||
    # These next few lines implement the following Django ORM
 | 
			
		||||
    # algorithm using raw SQL:
 | 
			
		||||
    ## for um in UserMessage.objects.filter(message_id__gte=cache_minimum_id).order_by("message"):
 | 
			
		||||
    ##     add_user_message(um.user_profile_id, um.message_id)
 | 
			
		||||
    # We do this because marshalling the Django objects is very
 | 
			
		||||
    # inefficient; total time consumed with the raw SQL is about
 | 
			
		||||
    # 600ms, vs. 3000ms-5000ms if we go through the ORM.
 | 
			
		||||
    cursor = connection.cursor()
 | 
			
		||||
    cursor.execute("SELECT user_profile_id, message_id from zerver_usermessage " +
 | 
			
		||||
                   "where message_id >= %s order by message_id", [cache_minimum_id])
 | 
			
		||||
    for row in cursor.fetchall():
 | 
			
		||||
        (user_profile_id, message_id) = row
 | 
			
		||||
        add_user_message(user_profile_id, message_id)
 | 
			
		||||
 | 
			
		||||
    streams = {}
 | 
			
		||||
    for stream in Stream.objects.select_related().all():
 | 
			
		||||
        streams[stream.id] = stream
 | 
			
		||||
    for m in (Message.objects.only("id", "recipient").select_related("recipient")
 | 
			
		||||
              .filter(id__gte=cache_minimum_id + (USERMESSAGE_CACHE_COUNT - STREAMMESSAGE_CACHE_COUNT),
 | 
			
		||||
                      recipient__type=Recipient.STREAM).order_by("id")):
 | 
			
		||||
        stream = streams[m.recipient.type_id]
 | 
			
		||||
        add_stream_message(stream.realm.id, stream.name, m.id)
 | 
			
		||||
 | 
			
		||||
    if not settings.DEPLOYED and not settings.TEST_SUITE:
 | 
			
		||||
        # Filling the memcached cache is a little slow, so do it in a child process.
 | 
			
		||||
        # For DEPLOYED cases, we run this from restart_server.
 | 
			
		||||
        subprocess.Popen(["python", os.path.join(os.path.dirname(__file__), "..", "manage.py"),
 | 
			
		||||
                          "fill_memcached_caches"])
 | 
			
		||||
 | 
			
		||||
def add_user_message(user_profile_id, message_id):
 | 
			
		||||
    add_table_message("user", user_profile_id, message_id)
 | 
			
		||||
 | 
			
		||||
def add_stream_message(realm_id, stream_name, message_id):
 | 
			
		||||
    add_table_message("stream", (realm_id, stream_name.lower()), message_id)
 | 
			
		||||
 | 
			
		||||
def add_table_message(table, key, message_id):
 | 
			
		||||
    if cache_minimum_id == sys.maxint:
 | 
			
		||||
        initialize_user_messages()
 | 
			
		||||
    mtables[table].setdefault(key, collections.deque(maxlen=400))
 | 
			
		||||
    mtables[table][key].appendleft(message_id)
 | 
			
		||||
 | 
			
		||||
def fetch_user_messages(user_profile_id, last):
 | 
			
		||||
    return fetch_table_messages("user", user_profile_id, last)
 | 
			
		||||
 | 
			
		||||
def fetch_stream_messages(realm_id, stream_name, last):
 | 
			
		||||
    return fetch_table_messages("stream", (realm_id, stream_name.lower()), last)
 | 
			
		||||
 | 
			
		||||
def fetch_table_messages(table, key, last):
 | 
			
		||||
    if cache_minimum_id == sys.maxint:
 | 
			
		||||
        initialize_user_messages()
 | 
			
		||||
 | 
			
		||||
    # We need to initialize the deque here for any new users or
 | 
			
		||||
    # streams that were created since Tornado was started
 | 
			
		||||
    mtables[table].setdefault(key, collections.deque(maxlen=400))
 | 
			
		||||
 | 
			
		||||
    # We need to do this check after initialize_user_messages has been called.
 | 
			
		||||
    if len(mtables[table][key]) == 0:
 | 
			
		||||
        # Since the request contains a value of "last", we can assume
 | 
			
		||||
        # that the relevant user or stream has actually received a
 | 
			
		||||
        # message, which means that mtabes[table][key] will not remain
 | 
			
		||||
        # empty after the below completes.
 | 
			
		||||
        #
 | 
			
		||||
        # Thus, we will run this code at most once per key (user or
 | 
			
		||||
        # stream that is being lurked on).  Further, we only do this
 | 
			
		||||
        # query for those keys that have not received a message since
 | 
			
		||||
        # cache_minimum_id.  So we can afford to do a database query
 | 
			
		||||
        # from Tornado in this case.
 | 
			
		||||
        if table == "user":
 | 
			
		||||
            logging.info("tornado: Doing database query for user %d" % (key,),)
 | 
			
		||||
            for um in reversed(UserMessage.objects.filter(user_profile_id=key).order_by('-message')[:400]):
 | 
			
		||||
                add_user_message(um.user_profile_id, um.message_id)
 | 
			
		||||
        elif table == "stream":
 | 
			
		||||
            logging.info("tornado: Doing database query for stream %s" % (key,))
 | 
			
		||||
            (realm_id, stream_name) = key
 | 
			
		||||
            stream = get_stream(stream_name, realm_id)
 | 
			
		||||
            # If a buggy client submits a "last" value with a nonexistent stream,
 | 
			
		||||
            # do nothing (and proceed to longpoll) rather than crashing.
 | 
			
		||||
            if stream is not None:
 | 
			
		||||
                recipient = Recipient.objects.get(type=Recipient.STREAM, type_id=stream.id)
 | 
			
		||||
                for m in Message.objects.only("id", "recipient").filter(recipient=recipient).order_by("id")[:400]:
 | 
			
		||||
                    add_stream_message(realm_id, stream_name, m.id)
 | 
			
		||||
 | 
			
		||||
    if len(mtables[table][key]) == 0:
 | 
			
		||||
        # Check the our assumption above that there are messages here.
 | 
			
		||||
        # If false, this may just mean a misbehaving client submitted
 | 
			
		||||
        # "last" even though it has no messages (in which case we
 | 
			
		||||
        # should proceed with longpolling by falling through).  But it
 | 
			
		||||
        # could also be a server bug, so we log a warning.
 | 
			
		||||
        logging.warning("Unexpected empty message queue for key %s!" % (key,))
 | 
			
		||||
    elif last < mtables[table][key][-1]:
 | 
			
		||||
        # The user's client has a way-too-old value for 'last'
 | 
			
		||||
        # (presumably 400 messages old), we should return an error
 | 
			
		||||
 | 
			
		||||
        # The error handler for get_updates in zulip.js parses this
 | 
			
		||||
        # message. If you change this message, you must update that
 | 
			
		||||
        # error handler.
 | 
			
		||||
        raise JsonableError("last value of %d too old!  Minimum valid is %d!" %
 | 
			
		||||
                            (last, mtables[table][key][-1]))
 | 
			
		||||
 | 
			
		||||
    message_list = []
 | 
			
		||||
    for message_id in mtables[table][key]:
 | 
			
		||||
        if message_id <= last:
 | 
			
		||||
            return reversed(message_list)
 | 
			
		||||
        message_list.append(message_id)
 | 
			
		||||
    return []
 | 
			
		||||
 | 
			
		||||
# The user receives this message
 | 
			
		||||
def user_receive_message(user_profile_id, message):
 | 
			
		||||
    add_user_message(user_profile_id, message.id)
 | 
			
		||||
    callbacks_table.call(user_profile_id, Callbacks.TYPE_USER_RECEIVE,
 | 
			
		||||
                         messages=[message], update_types=["new_messages"])
 | 
			
		||||
 | 
			
		||||
# The stream receives this message
 | 
			
		||||
def stream_receive_message(realm_id, stream_name, message):
 | 
			
		||||
    add_stream_message(realm_id, stream_name, message.id)
 | 
			
		||||
    callbacks_table.call((realm_id, stream_name.lower()),
 | 
			
		||||
                         Callbacks.TYPE_STREAM_RECEIVE,
 | 
			
		||||
                         messages=[message], update_types=["new_messages"])
 | 
			
		||||
 | 
			
		||||
def update_pointer(user_profile_id, new_pointer):
 | 
			
		||||
    callbacks_table.call(user_profile_id, Callbacks.TYPE_POINTER_UPDATE,
 | 
			
		||||
                         new_pointer=new_pointer,
 | 
			
		||||
                         update_types=["pointer_update"])
 | 
			
		||||
 | 
			
		||||
    event = dict(type='pointer', pointer=new_pointer)
 | 
			
		||||
    for client in get_client_descriptors_for_user(user_profile_id):
 | 
			
		||||
        if client.accepts_event_type(event['type']):
 | 
			
		||||
@@ -367,8 +161,6 @@ def process_new_message(data):
 | 
			
		||||
        user_profile = user_profiles[user_data['id']]
 | 
			
		||||
        flags = user_data.get('flags', [])
 | 
			
		||||
 | 
			
		||||
        user_receive_message(user_profile_id, message)
 | 
			
		||||
 | 
			
		||||
        for client in get_client_descriptors_for_user(user_profile_id):
 | 
			
		||||
            send_to_clients[client.event_queue.id] = (client, flags)
 | 
			
		||||
 | 
			
		||||
@@ -408,9 +200,6 @@ def process_new_message(data):
 | 
			
		||||
        event = dict(type='message', message=message_dict, flags=flags)
 | 
			
		||||
        client.add_event(event)
 | 
			
		||||
 | 
			
		||||
    if 'stream_name' in data:
 | 
			
		||||
        stream_receive_message(data['realm_id'], data['stream_name'], message)
 | 
			
		||||
 | 
			
		||||
def process_event(data):
 | 
			
		||||
    event = data['event']
 | 
			
		||||
    for user_profile_id in data['users']:
 | 
			
		||||
 
 | 
			
		||||
@@ -1,26 +1,18 @@
 | 
			
		||||
from __future__ import absolute_import
 | 
			
		||||
 | 
			
		||||
from django.conf import settings
 | 
			
		||||
from django.views.decorators.csrf import csrf_exempt
 | 
			
		||||
from zerver.models import get_client
 | 
			
		||||
 | 
			
		||||
from zerver.decorator import asynchronous, authenticated_api_view, \
 | 
			
		||||
from zerver.decorator import asynchronous, \
 | 
			
		||||
    authenticated_json_post_view, internal_notify_view, RespondAsynchronously, \
 | 
			
		||||
    has_request_variables, to_non_negative_int, json_to_bool, json_to_list, \
 | 
			
		||||
    REQ
 | 
			
		||||
    has_request_variables, json_to_bool, json_to_list, REQ
 | 
			
		||||
 | 
			
		||||
from zerver.lib.response import json_success, json_error
 | 
			
		||||
from zerver.middleware import async_request_restart
 | 
			
		||||
from zerver.tornado_callbacks import \
 | 
			
		||||
    fetch_stream_messages, fetch_user_messages, \
 | 
			
		||||
    add_stream_receive_callback, add_user_receive_callback, \
 | 
			
		||||
    add_pointer_update_callback, process_notification
 | 
			
		||||
from zerver.tornado_callbacks import process_notification
 | 
			
		||||
 | 
			
		||||
from zerver.lib.cache_helpers import cache_get_message
 | 
			
		||||
from zerver.lib.event_queue import allocate_client_descriptor, get_client_descriptor
 | 
			
		||||
 | 
			
		||||
import ujson
 | 
			
		||||
import socket
 | 
			
		||||
 | 
			
		||||
from zerver.lib.rest import rest_dispatch as _rest_dispatch
 | 
			
		||||
rest_dispatch = csrf_exempt((lambda request, *args, **kwargs: _rest_dispatch(request, globals(), *args, **kwargs)))
 | 
			
		||||
@@ -30,162 +22,6 @@ def notify(request):
 | 
			
		||||
    process_notification(ujson.loads(request.POST['data']))
 | 
			
		||||
    return json_success()
 | 
			
		||||
 | 
			
		||||
@authenticated_json_post_view
 | 
			
		||||
def json_get_updates(request, user_profile):
 | 
			
		||||
    return get_updates_backend(request, user_profile,
 | 
			
		||||
                               client=request.client, apply_markdown=True)
 | 
			
		||||
 | 
			
		||||
@authenticated_api_view
 | 
			
		||||
def api_get_messages(request, user_profile):
 | 
			
		||||
    return get_messages_backend(request, user_profile)
 | 
			
		||||
 | 
			
		||||
def get_messages_backend(request, user_profile):
 | 
			
		||||
    return get_updates_backend(request, user_profile, client=request.client)
 | 
			
		||||
 | 
			
		||||
def format_updates_response(messages=[], apply_markdown=True,
 | 
			
		||||
                            user_profile=None, new_pointer=None,
 | 
			
		||||
                            client=None, update_types=[],
 | 
			
		||||
                            client_server_generation=None):
 | 
			
		||||
    if client is not None and client.name.endswith("_mirror"):
 | 
			
		||||
        messages = [m for m in messages if m.sending_client.name != client.name]
 | 
			
		||||
    ret = {'messages': [message.to_dict(apply_markdown) for message in messages],
 | 
			
		||||
           "result": "success",
 | 
			
		||||
           "msg": "",
 | 
			
		||||
           'update_types': update_types}
 | 
			
		||||
    if client_server_generation is not None:
 | 
			
		||||
        ret['server_generation'] = settings.SERVER_GENERATION
 | 
			
		||||
    if new_pointer is not None:
 | 
			
		||||
        ret['new_pointer'] = new_pointer
 | 
			
		||||
 | 
			
		||||
    return ret
 | 
			
		||||
 | 
			
		||||
def return_messages_immediately(user_profile, last,
 | 
			
		||||
                                client_server_generation,
 | 
			
		||||
                                client_pointer, dont_block,
 | 
			
		||||
                                stream_name, **kwargs):
 | 
			
		||||
    update_types = []
 | 
			
		||||
    new_pointer = None
 | 
			
		||||
    if dont_block:
 | 
			
		||||
        update_types.append("nonblocking_request")
 | 
			
		||||
 | 
			
		||||
    if (client_server_generation is not None and
 | 
			
		||||
        client_server_generation != settings.SERVER_GENERATION):
 | 
			
		||||
        update_types.append("client_reload")
 | 
			
		||||
 | 
			
		||||
    ptr = user_profile.pointer
 | 
			
		||||
    if (client_pointer is not None and ptr > client_pointer):
 | 
			
		||||
        new_pointer = ptr
 | 
			
		||||
        update_types.append("pointer_update")
 | 
			
		||||
 | 
			
		||||
    if last is not None:
 | 
			
		||||
        if stream_name is not None:
 | 
			
		||||
            message_ids = fetch_stream_messages(user_profile.realm.id, stream_name, last)
 | 
			
		||||
        else:
 | 
			
		||||
            message_ids = fetch_user_messages(user_profile.id, last)
 | 
			
		||||
        messages = map(cache_get_message, message_ids)
 | 
			
		||||
 | 
			
		||||
        # Filter for mirroring before checking whether there are any
 | 
			
		||||
        # messages to pass on.  If we don't do this, when the only message
 | 
			
		||||
        # to forward is one that was sent via the mirroring, the API
 | 
			
		||||
        # client will end up in an endless loop requesting more data from
 | 
			
		||||
        # us.
 | 
			
		||||
        if "client" in kwargs and kwargs["client"].name.endswith("_mirror"):
 | 
			
		||||
            messages = [m for m in messages if
 | 
			
		||||
                        m.sending_client.name != kwargs["client"].name]
 | 
			
		||||
    else: # last is None, so we're not interested in any old messages
 | 
			
		||||
        messages = []
 | 
			
		||||
 | 
			
		||||
    if messages:
 | 
			
		||||
        update_types.append("new_messages")
 | 
			
		||||
 | 
			
		||||
    if update_types:
 | 
			
		||||
        return format_updates_response(messages=messages,
 | 
			
		||||
                                       user_profile=user_profile,
 | 
			
		||||
                                       new_pointer=new_pointer,
 | 
			
		||||
                                       client_server_generation=client_server_generation,
 | 
			
		||||
                                       update_types=update_types,
 | 
			
		||||
                                       **kwargs)
 | 
			
		||||
 | 
			
		||||
    return None
 | 
			
		||||
 | 
			
		||||
# Note: We allow any stream name at all here! Validation and
 | 
			
		||||
# authorization (is the stream "public") are handled by the caller of
 | 
			
		||||
# notify new_message. If a user makes a get_updates request for a
 | 
			
		||||
# nonexistent or non-public stream, they won't get an error -- they'll
 | 
			
		||||
# just never receive any messages.
 | 
			
		||||
@asynchronous
 | 
			
		||||
@has_request_variables
 | 
			
		||||
def get_updates_backend(request, user_profile, handler=None,
 | 
			
		||||
                        last = REQ(converter=to_non_negative_int, default=None),
 | 
			
		||||
                        client_server_generation = REQ(whence='server_generation', default=None,
 | 
			
		||||
                                                        converter=int),
 | 
			
		||||
                        client_pointer = REQ(whence='pointer', converter=int, default=None),
 | 
			
		||||
                        dont_block = REQ(converter=json_to_bool, default=False),
 | 
			
		||||
                        stream_name = REQ(default=None),
 | 
			
		||||
                        apply_markdown = REQ(default=False, converter=json_to_bool),
 | 
			
		||||
                        **kwargs):
 | 
			
		||||
    resp = return_messages_immediately(user_profile, last,
 | 
			
		||||
                                       client_server_generation,
 | 
			
		||||
                                       client_pointer,
 | 
			
		||||
                                       dont_block, stream_name,
 | 
			
		||||
                                       apply_markdown=apply_markdown, **kwargs)
 | 
			
		||||
    if resp is not None:
 | 
			
		||||
        handler.zulip_finish(resp, request, apply_markdown)
 | 
			
		||||
 | 
			
		||||
        # We have already invoked handler.zulip_finish(), so we bypass the usual view
 | 
			
		||||
        # response path.  We are "responding asynchronously" except that it
 | 
			
		||||
        # already happened.  This is slightly weird.
 | 
			
		||||
        return RespondAsynchronously
 | 
			
		||||
 | 
			
		||||
    # Enter long-polling mode.
 | 
			
		||||
    #
 | 
			
		||||
    # Instead of responding to the client right away, leave our connection open
 | 
			
		||||
    # and return to the Tornado main loop.  One of the notify_* views will
 | 
			
		||||
    # eventually invoke one of these callbacks, which will send the delayed
 | 
			
		||||
    # response.
 | 
			
		||||
 | 
			
		||||
    def cb(**cb_kwargs):
 | 
			
		||||
        async_request_restart(request)
 | 
			
		||||
        if handler.request.connection.stream.closed():
 | 
			
		||||
            return
 | 
			
		||||
        try:
 | 
			
		||||
            # It would be nice to be able to do these checks in
 | 
			
		||||
            # UserProfile.receive, but it doesn't know what the value
 | 
			
		||||
            # of "last" was for each callback.
 | 
			
		||||
            if last is not None and "messages" in cb_kwargs:
 | 
			
		||||
                messages = cb_kwargs["messages"]
 | 
			
		||||
 | 
			
		||||
                # Make sure the client doesn't get a message twice
 | 
			
		||||
                # when messages are processed out of order.
 | 
			
		||||
                if messages[0].id <= last:
 | 
			
		||||
                    # We must return a response because we don't have
 | 
			
		||||
                    # a way to re-queue a callback and so the client
 | 
			
		||||
                    # must do it by making a new request
 | 
			
		||||
                    handler.zulip_finish({"result": "success",
 | 
			
		||||
                                          "msg": "",
 | 
			
		||||
                                          'update_types': []},
 | 
			
		||||
                                         request, apply_markdown)
 | 
			
		||||
                    return
 | 
			
		||||
 | 
			
		||||
            kwargs.update(cb_kwargs)
 | 
			
		||||
            res = format_updates_response(user_profile=user_profile,
 | 
			
		||||
                                          client_server_generation=client_server_generation,
 | 
			
		||||
                                          apply_markdown=apply_markdown,
 | 
			
		||||
                                          **kwargs)
 | 
			
		||||
            handler.zulip_finish(res, request, apply_markdown)
 | 
			
		||||
        except socket.error:
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
    if stream_name is not None:
 | 
			
		||||
        add_stream_receive_callback(user_profile.realm.id, stream_name, handler.async_callback(cb))
 | 
			
		||||
    else:
 | 
			
		||||
        add_user_receive_callback(user_profile, handler.async_callback(cb))
 | 
			
		||||
    if client_pointer is not None:
 | 
			
		||||
        add_pointer_update_callback(user_profile, handler.async_callback(cb))
 | 
			
		||||
 | 
			
		||||
    # runtornado recognizes this special return value.
 | 
			
		||||
    return RespondAsynchronously
 | 
			
		||||
 | 
			
		||||
@authenticated_json_post_view
 | 
			
		||||
def json_get_events(request, user_profile):
 | 
			
		||||
    return get_events_backend(request, user_profile, apply_markdown=True)
 | 
			
		||||
 
 | 
			
		||||
@@ -223,8 +223,6 @@ v1_api_and_json_patterns = patterns('zerver.views',
 | 
			
		||||
            {'POST': 'api_events_register'}),
 | 
			
		||||
 | 
			
		||||
) + patterns('zerver.tornadoviews',
 | 
			
		||||
    url(r'^messages/latest$', 'rest_dispatch',
 | 
			
		||||
        {'GET': 'get_updates_backend'}),
 | 
			
		||||
    url(r'^events$', 'rest_dispatch',
 | 
			
		||||
        {'GET': 'get_events_backend'}),
 | 
			
		||||
)
 | 
			
		||||
@@ -237,8 +235,6 @@ if not settings.LOCAL_SERVER:
 | 
			
		||||
 | 
			
		||||
urlpatterns += patterns('zerver.tornadoviews',
 | 
			
		||||
    # Tornado views
 | 
			
		||||
    url(r'^api/v1/get_messages$',           'api_get_messages'),
 | 
			
		||||
    url(r'^json/get_updates$',              'json_get_updates'),
 | 
			
		||||
    url(r'^json/get_events$',               'json_get_events'),
 | 
			
		||||
    # Used internally for communication between Django and Tornado processes
 | 
			
		||||
    url(r'^notify_tornado$',                'notify'),
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user