events: Extract apply_event helper.

This mostly just saves us a level of messy indentation.
This commit is contained in:
Tim Abbott
2017-02-20 11:09:48 -08:00
parent e4fbad95c6
commit f52d812a71
2 changed files with 203 additions and 199 deletions

View File

@@ -36,7 +36,7 @@ interacting with the database in `zerver/lib/actions.py`. It should
update the database and send an event announcing the change. update the database and send an event announcing the change.
**Application state:** Modify the `fetch_initial_state_data` and **Application state:** Modify the `fetch_initial_state_data` and
`apply_events` functions in `zerver/lib/actions.py` to update the state `apply_event` functions in `zerver/lib/actions.py` to update the state
based on the event you just created. based on the event you just created.
**Backend implementation:** Make any other modifications to the backend **Backend implementation:** Make any other modifications to the backend
@@ -182,16 +182,16 @@ realm. :
You then need to add code that will handle the event and update the You then need to add code that will handle the event and update the
application state. In `zerver/lib/events.py` update the application state. In `zerver/lib/events.py` update the
`fetch_initial_state` and `apply_events` functions. : `fetch_initial_state` and `apply_event` functions. :
def fetch_initial_state_data(user_profile, event_types, queue_id): def fetch_initial_state_data(user_profile, event_types, queue_id, include_subscribers=True):
# ... # ...
state['realm_invite_by_admins_only'] = user_profile.realm.invite_by_admins_only` state['realm_invite_by_admins_only'] = user_profile.realm.invite_by_admins_only`
In this case you don't need to change `apply_events` because there is In this case you don't need to change `apply_event` because there is
already code that will correctly handle the realm update event type: : already code that will correctly handle the realm update event type: :
def apply_events(state, events, user_profile): def apply_event(state, events, user_profile, include_subscribers):
for event in events: for event in events:
# ... # ...
elif event['type'] == 'realm': elif event['type'] == 'realm':

View File

@@ -152,219 +152,223 @@ def fetch_initial_state_data(user_profile, event_types, queue_id,
def apply_events(state, events, user_profile, include_subscribers=True): def apply_events(state, events, user_profile, include_subscribers=True):
# type: (Dict[str, Any], Iterable[Dict[str, Any]], UserProfile, bool) -> None # type: (Dict[str, Any], Iterable[Dict[str, Any]], UserProfile, bool) -> None
for event in events: for event in events:
if event['type'] == "message": apply_event(state, event, user_profile, include_subscribers)
state['max_message_id'] = max(state['max_message_id'], event['message']['id'])
elif event['type'] == "pointer":
state['pointer'] = max(state['pointer'], event['pointer'])
elif event['type'] == "realm_user":
person = event['person']
def our_person(p): def apply_event(state, event, user_profile, include_subscribers):
# type: (Dict[str, Any]) -> bool # type: (Dict[str, Any], Dict[str, Any], UserProfile, bool) -> None
return p['user_id'] == person['user_id'] if event['type'] == "message":
state['max_message_id'] = max(state['max_message_id'], event['message']['id'])
elif event['type'] == "pointer":
state['pointer'] = max(state['pointer'], event['pointer'])
elif event['type'] == "realm_user":
person = event['person']
if event['op'] == "add": def our_person(p):
state['realm_users'].append(person) # type: (Dict[str, Any]) -> bool
elif event['op'] == "remove": return p['user_id'] == person['user_id']
state['realm_users'] = [user for user in state['realm_users'] if not our_person(user)]
elif event['op'] == 'update':
for p in state['realm_users']:
if our_person(p):
# In the unlikely event that the current user
# just changed to/from being an admin, we need
# to add/remove the data on all bots in the
# realm. This is ugly and probably better
# solved by removing the all-realm-bots data
# given to admin users from this flow.
if ('is_admin' in person and 'realm_bots' in state and
user_profile.email == person['email']):
if p['is_admin'] and not person['is_admin']:
state['realm_bots'] = []
if not p['is_admin'] and person['is_admin']:
state['realm_bots'] = get_owned_bot_dicts(user_profile)
# Now update the person
p.update(person)
elif event['type'] == 'realm_bot':
if event['op'] == 'add':
state['realm_bots'].append(event['bot'])
if event['op'] == 'remove': if event['op'] == "add":
email = event['bot']['email'] state['realm_users'].append(person)
state['realm_bots'] = [b for b in state['realm_bots'] if b['email'] != email] elif event['op'] == "remove":
state['realm_users'] = [user for user in state['realm_users'] if not our_person(user)]
elif event['op'] == 'update':
for p in state['realm_users']:
if our_person(p):
# In the unlikely event that the current user
# just changed to/from being an admin, we need
# to add/remove the data on all bots in the
# realm. This is ugly and probably better
# solved by removing the all-realm-bots data
# given to admin users from this flow.
if ('is_admin' in person and 'realm_bots' in state and
user_profile.email == person['email']):
if p['is_admin'] and not person['is_admin']:
state['realm_bots'] = []
if not p['is_admin'] and person['is_admin']:
state['realm_bots'] = get_owned_bot_dicts(user_profile)
# Now update the person
p.update(person)
elif event['type'] == 'realm_bot':
if event['op'] == 'add':
state['realm_bots'].append(event['bot'])
if event['op'] == 'update': if event['op'] == 'remove':
for bot in state['realm_bots']: email = event['bot']['email']
if bot['email'] == event['bot']['email']: state['realm_bots'] = [b for b in state['realm_bots'] if b['email'] != email]
bot.update(event['bot'])
elif event['type'] == 'stream': if event['op'] == 'update':
if event['op'] == 'create': for bot in state['realm_bots']:
for stream in event['streams']: if bot['email'] == event['bot']['email']:
if not stream['invite_only']: bot.update(event['bot'])
stream_data = copy.deepcopy(stream)
if include_subscribers:
stream_data['subscribers'] = []
# Add stream to never_subscribed (if not invite_only)
state['never_subscribed'].append(stream_data)
if event['op'] == 'delete': elif event['type'] == 'stream':
deleted_stream_ids = {stream['stream_id'] for stream in event['streams']} if event['op'] == 'create':
state['streams'] = [s for s in state['streams'] if s['stream_id'] not in deleted_stream_ids] for stream in event['streams']:
state['never_subscribed'] = [stream for stream in state['never_subscribed'] if if not stream['invite_only']:
stream['stream_id'] not in deleted_stream_ids] stream_data = copy.deepcopy(stream)
if include_subscribers:
stream_data['subscribers'] = []
# Add stream to never_subscribed (if not invite_only)
state['never_subscribed'].append(stream_data)
if event['op'] == 'update': if event['op'] == 'delete':
# For legacy reasons, we call stream data 'subscriptions' in deleted_stream_ids = {stream['stream_id'] for stream in event['streams']}
# the state var here, for the benefit of the JS code. state['streams'] = [s for s in state['streams'] if s['stream_id'] not in deleted_stream_ids]
for obj in state['subscriptions']: state['never_subscribed'] = [stream for stream in state['never_subscribed'] if
if obj['name'].lower() == event['name'].lower(): stream['stream_id'] not in deleted_stream_ids]
obj[event['property']] = event['value']
# Also update the pure streams data
for stream in state['streams']:
if stream['name'].lower() == event['name'].lower():
prop = event['property']
if prop in stream:
stream[prop] = event['value']
elif event['op'] == "occupy":
state['streams'] += event['streams']
elif event['op'] == "vacate":
stream_ids = [s["stream_id"] for s in event['streams']]
state['streams'] = [s for s in state['streams'] if s["stream_id"] not in stream_ids]
elif event['type'] == 'default_streams':
state['realm_default_streams'] = event['default_streams']
elif event['type'] == 'realm':
if event['op'] == "update":
field = 'realm_' + event['property']
state[field] = event['value']
elif event['op'] == "update_dict":
for key, value in event['data'].items():
state['realm_' + key] = value
elif event['type'] == "subscription":
if not include_subscribers and event['op'] in ['peer_add', 'peer_remove']:
continue
if event['op'] in ["add"]: if event['op'] == 'update':
if include_subscribers: # For legacy reasons, we call stream data 'subscriptions' in
# Convert the emails to user_profile IDs since that's what register() returns # the state var here, for the benefit of the JS code.
# TODO: Clean up this situation by making the event also have IDs for obj in state['subscriptions']:
for item in event["subscriptions"]: if obj['name'].lower() == event['name'].lower():
item["subscribers"] = [get_user_profile_by_email(email).id for email in item["subscribers"]] obj[event['property']] = event['value']
else: # Also update the pure streams data
# Avoid letting 'subscribers' entries end up in the list for stream in state['streams']:
for i, sub in enumerate(event['subscriptions']): if stream['name'].lower() == event['name'].lower():
event['subscriptions'][i] = copy.deepcopy(event['subscriptions'][i]) prop = event['property']
del event['subscriptions'][i]['subscribers'] if prop in stream:
stream[prop] = event['value']
elif event['op'] == "occupy":
state['streams'] += event['streams']
elif event['op'] == "vacate":
stream_ids = [s["stream_id"] for s in event['streams']]
state['streams'] = [s for s in state['streams'] if s["stream_id"] not in stream_ids]
elif event['type'] == 'default_streams':
state['realm_default_streams'] = event['default_streams']
elif event['type'] == 'realm':
if event['op'] == "update":
field = 'realm_' + event['property']
state[field] = event['value']
elif event['op'] == "update_dict":
for key, value in event['data'].items():
state['realm_' + key] = value
elif event['type'] == "subscription":
if not include_subscribers and event['op'] in ['peer_add', 'peer_remove']:
return
def name(sub): if event['op'] in ["add"]:
# type: (Dict[str, Any]) -> Text if include_subscribers:
return sub['name'].lower() # Convert the emails to user_profile IDs since that's what register() returns
# TODO: Clean up this situation by making the event also have IDs
for item in event["subscriptions"]:
item["subscribers"] = [get_user_profile_by_email(email).id for email in item["subscribers"]]
else:
# Avoid letting 'subscribers' entries end up in the list
for i, sub in enumerate(event['subscriptions']):
event['subscriptions'][i] = copy.deepcopy(event['subscriptions'][i])
del event['subscriptions'][i]['subscribers']
if event['op'] == "add": def name(sub):
added_names = set(map(name, event["subscriptions"])) # type: (Dict[str, Any]) -> Text
was_added = lambda s: name(s) in added_names return sub['name'].lower()
# add the new subscriptions if event['op'] == "add":
state['subscriptions'] += event['subscriptions'] added_names = set(map(name, event["subscriptions"]))
was_added = lambda s: name(s) in added_names
# remove them from unsubscribed if they had been there # add the new subscriptions
state['unsubscribed'] = [s for s in state['unsubscribed'] if not was_added(s)] state['subscriptions'] += event['subscriptions']
# remove them from never_subscribed if they had been there # remove them from unsubscribed if they had been there
state['never_subscribed'] = [s for s in state['never_subscribed'] if not was_added(s)] state['unsubscribed'] = [s for s in state['unsubscribed'] if not was_added(s)]
elif event['op'] == "remove": # remove them from never_subscribed if they had been there
removed_names = set(map(name, event["subscriptions"])) state['never_subscribed'] = [s for s in state['never_subscribed'] if not was_added(s)]
was_removed = lambda s: name(s) in removed_names
# Find the subs we are affecting. elif event['op'] == "remove":
removed_subs = list(filter(was_removed, state['subscriptions'])) removed_names = set(map(name, event["subscriptions"]))
was_removed = lambda s: name(s) in removed_names
# Remove our user from the subscribers of the removed subscriptions. # Find the subs we are affecting.
if include_subscribers: removed_subs = list(filter(was_removed, state['subscriptions']))
for sub in removed_subs:
sub['subscribers'] = [id for id in sub['subscribers'] if id != user_profile.id]
# We must effectively copy the removed subscriptions from subscriptions to # Remove our user from the subscribers of the removed subscriptions.
# unsubscribe, since we only have the name in our data structure. if include_subscribers:
state['unsubscribed'] += removed_subs for sub in removed_subs:
sub['subscribers'] = [id for id in sub['subscribers'] if id != user_profile.id]
# Now filter out the removed subscriptions from subscriptions. # We must effectively copy the removed subscriptions from subscriptions to
state['subscriptions'] = [s for s in state['subscriptions'] if not was_removed(s)] # unsubscribe, since we only have the name in our data structure.
state['unsubscribed'] += removed_subs
elif event['op'] == 'update': # Now filter out the removed subscriptions from subscriptions.
for sub in state['subscriptions']: state['subscriptions'] = [s for s in state['subscriptions'] if not was_removed(s)]
if sub['name'].lower() == event['name'].lower():
sub[event['property']] = event['value'] elif event['op'] == 'update':
elif event['op'] == 'peer_add': for sub in state['subscriptions']:
user_id = event['user_id'] if sub['name'].lower() == event['name'].lower():
for sub in state['subscriptions']: sub[event['property']] = event['value']
if (sub['name'] in event['subscriptions'] and elif event['op'] == 'peer_add':
user_id not in sub['subscribers']): user_id = event['user_id']
sub['subscribers'].append(user_id) for sub in state['subscriptions']:
for sub in state['never_subscribed']: if (sub['name'] in event['subscriptions'] and
if (sub['name'] in event['subscriptions'] and user_id not in sub['subscribers']):
user_id not in sub['subscribers']): sub['subscribers'].append(user_id)
sub['subscribers'].append(user_id) for sub in state['never_subscribed']:
elif event['op'] == 'peer_remove': if (sub['name'] in event['subscriptions'] and
user_id = event['user_id'] user_id not in sub['subscribers']):
for sub in state['subscriptions']: sub['subscribers'].append(user_id)
if (sub['name'] in event['subscriptions'] and elif event['op'] == 'peer_remove':
user_id in sub['subscribers']): user_id = event['user_id']
sub['subscribers'].remove(user_id) for sub in state['subscriptions']:
elif event['type'] == "presence": if (sub['name'] in event['subscriptions'] and
state['presences'][event['email']] = event['presence'] user_id in sub['subscribers']):
elif event['type'] == "update_message": sub['subscribers'].remove(user_id)
# The client will get the updated message directly elif event['type'] == "presence":
pass state['presences'][event['email']] = event['presence']
elif event['type'] == "reaction": elif event['type'] == "update_message":
# The client will get the message with the reactions directly # The client will get the updated message directly
pass pass
elif event['type'] == "referral": elif event['type'] == "reaction":
state['referrals'] = event['referrals'] # The client will get the message with the reactions directly
elif event['type'] == "update_message_flags": pass
# The client will get the message with the updated flags directly elif event['type'] == "referral":
pass state['referrals'] = event['referrals']
elif event['type'] == "realm_domains": elif event['type'] == "update_message_flags":
if event['op'] == 'add': # The client will get the message with the updated flags directly
state['realm_domains'].append(event['alias']) pass
elif event['op'] == 'change': elif event['type'] == "realm_domains":
for realm_domain in state['realm_domains']: if event['op'] == 'add':
if realm_domain['domain'] == event['alias']['domain']: state['realm_domains'].append(event['alias'])
realm_domain['allow_subdomains'] = event['alias']['allow_subdomains'] elif event['op'] == 'change':
elif event['op'] == 'remove': for realm_domain in state['realm_domains']:
state['realm_domains'] = [alias for alias in state['realm_domains'] if alias['domain'] != event['domain']] if realm_domain['domain'] == event['alias']['domain']:
elif event['type'] == "realm_emoji": realm_domain['allow_subdomains'] = event['alias']['allow_subdomains']
state['realm_emoji'] = event['realm_emoji'] elif event['op'] == 'remove':
elif event['type'] == "alert_words": state['realm_domains'] = [alias for alias in state['realm_domains'] if alias['domain'] != event['domain']]
state['alert_words'] = event['alert_words'] elif event['type'] == "realm_emoji":
elif event['type'] == "muted_topics": state['realm_emoji'] = event['realm_emoji']
state['muted_topics'] = event["muted_topics"] elif event['type'] == "alert_words":
elif event['type'] == "realm_filters": state['alert_words'] = event['alert_words']
state['realm_filters'] = event["realm_filters"] elif event['type'] == "muted_topics":
elif event['type'] == "update_display_settings": state['muted_topics'] = event["muted_topics"]
if event['setting_name'] == "twenty_four_hour_time": elif event['type'] == "realm_filters":
state['twenty_four_hour_time'] = event["setting"] state['realm_filters'] = event["realm_filters"]
if event['setting_name'] == 'left_side_userlist': elif event['type'] == "update_display_settings":
state['left_side_userlist'] = event["setting"] if event['setting_name'] == "twenty_four_hour_time":
elif event['type'] == "update_global_notifications": state['twenty_four_hour_time'] = event["setting"]
if event['notification_name'] == "enable_stream_desktop_notifications": if event['setting_name'] == 'left_side_userlist':
state['enable_stream_desktop_notifications'] = event['setting'] state['left_side_userlist'] = event["setting"]
elif event['notification_name'] == "enable_stream_sounds": elif event['type'] == "update_global_notifications":
state['enable_stream_sounds'] = event['setting'] if event['notification_name'] == "enable_stream_desktop_notifications":
elif event['notification_name'] == "enable_desktop_notifications": state['enable_stream_desktop_notifications'] = event['setting']
state['enable_desktop_notifications'] = event['setting'] elif event['notification_name'] == "enable_stream_sounds":
elif event['notification_name'] == "enable_sounds": state['enable_stream_sounds'] = event['setting']
state['enable_sounds'] = event['setting'] elif event['notification_name'] == "enable_desktop_notifications":
elif event['notification_name'] == "enable_offline_email_notifications": state['enable_desktop_notifications'] = event['setting']
state['enable_offline_email_notifications'] = event['setting'] elif event['notification_name'] == "enable_sounds":
elif event['notification_name'] == "enable_offline_push_notifications": state['enable_sounds'] = event['setting']
state['enable_offline_push_notifications'] = event['setting'] elif event['notification_name'] == "enable_offline_email_notifications":
elif event['notification_name'] == "enable_online_push_notifications": state['enable_offline_email_notifications'] = event['setting']
state['enable_online_push_notifications'] = event['setting'] elif event['notification_name'] == "enable_offline_push_notifications":
elif event['notification_name'] == "enable_digest_emails": state['enable_offline_push_notifications'] = event['setting']
state['enable_digest_emails'] = event['setting'] elif event['notification_name'] == "enable_online_push_notifications":
else: state['enable_online_push_notifications'] = event['setting']
raise ValueError("Unexpected event type %s" % (event['type'],)) elif event['notification_name'] == "enable_digest_emails":
state['enable_digest_emails'] = event['setting']
else:
raise ValueError("Unexpected event type %s" % (event['type'],))
def do_events_register(user_profile, user_client, apply_markdown=True, def do_events_register(user_profile, user_client, apply_markdown=True,
event_types=None, queue_lifespan_secs=0, all_public_streams=False, event_types=None, queue_lifespan_secs=0, all_public_streams=False,