@gen.coroutine
def QueryNotifications(client, obj_store, user_id, device_id, request):
  """Queries a list of pending notifications for a user since the last
  request.
  """
  # Clients are not allowed to request long polling for more than this duration.
  MAX_LONG_POLL = 300
  # Within our fake long polling, poll the database this often.
  LONG_POLL_INTERVAL = 5

  start_key = int(request.get('start_key')) if 'start_key' in request else None
  limit = request.get('limit', None)
  scan_forward = request.get('scan_forward', True)
  max_long_poll = min(int(request.get('max_long_poll', 0)),
                      MAX_LONG_POLL)
  if max_long_poll > 0:
    deadline = IOLoop.current().time() + max_long_poll
  else:
    deadline = None

  while True:
    notifications = yield NotificationManager.QuerySince(client,
                                                         user_id,
                                                         device_id,
                                                         start_key,
                                                         limit=limit,
                                                         scan_forward=scan_forward)
    if len(notifications) > 0 or deadline is None:
      break

    now = IOLoop.current().time()
    next_try = now + LONG_POLL_INTERVAL
    if next_try >= deadline:
      # No point in scheduling a query after we expect our client to go away.
      break

    vf_context = base.ViewfinderContext.current()
    assert vf_context.connection_close_event is not None
    try:
      yield vf_context.connection_close_event.wait(next_try)
    except toro.Timeout:
      # The close event timed out, meaning the client is still connected
      # and it's time to try again.
      pass
    else:
      # The close event triggered without timing out, so give up.
      assert vf_context.connection_close_event.is_set()
      break

  # At this point we either have non-empty results, the client is not
  # in long-polling mode, the long-polling deadline has expired, or
  # the client has disconnected. We could short-circuit in the latter
  # case, but it doesn't hurt to go ahead and generate the empty
  # result.

  # Get any activities that are associated with the notifications in a single batch query.
  activity_keys = [db_client.DBKey(n.viewpoint_id, n.activity_id)
                   for n in notifications
                   if n.activity_id is not None]
  activities = yield gen.Task(Activity.BatchQuery, client, activity_keys, None)

  response = {'notifications': []}
  if len(notifications) > 0:
    response['last_key'] = www_util.FormatIntegralLastKey(notifications[-1].notification_id)

  for notification in notifications:
    notification_dict = {'notification_id': notification.notification_id,
                         'name': notification.name,
                         'sender_id': notification.sender_id,
                         'timestamp': notification.timestamp}

    util.SetIfNotNone(notification_dict, 'op_id', notification.op_id)

    invalidate = notification.GetInvalidate()
    util.SetIfNotNone(notification_dict, 'invalidate', invalidate)

    # If the operation modified a viewpoint's update_seq or viewed_seq values, in-line them.
    if notification.update_seq is not None or notification.viewed_seq is not None:
      vp_dict = notification_dict.setdefault('inline', {}).setdefault('viewpoint', {})
      vp_dict['viewpoint_id'] = notification.viewpoint_id
      if notification.update_seq is not None:
        vp_dict['update_seq'] = notification.update_seq
      if notification.viewed_seq is not None:
        vp_dict['viewed_seq'] = notification.viewed_seq

    # If the operation added an activity, in-line it.
    if notification.activity_id is not None:
      activity = activities.pop(0)

      # Project all activity columns, but nest the json column underneath a key called activity.name.
      activity_dict = activity.MakeMetadataDict()
      notification_dict.setdefault('inline', {})['activity'] = activity_dict

      # If this was a post_comment notification, in-line the comment metadata if no invalidation exists.
      # NOTE: If/when we allow unfollowing viewpoints, permissions would have to be checked to ensure that
      #       the calling user is still a follower of the viewpoint containing the comment.
      if activity.name == 'post_comment' and notification.invalidate is None:
        comment_id = activity_dict['post_comment']['comment_id']
        comment = yield gen.Task(Comment.Query, client, notification.viewpoint_id, comment_id, col_names=None)
        notification_dict['inline']['comment'] = comment._asdict()

    response['notifications'].append(notification_dict)

  if len(response['notifications']) > 0:
    # Query usage data and inline it into the last notification in the response.
    def _AccountingAsDict(act):
      if act is None:
        return None
      act_dict = act._asdict()
      act_dict.pop('hash_key', None)
      act_dict.pop('sort_key', None)
      act_dict.pop('op_ids', None)
      return act_dict

    owned, shared, visible = yield gen.Task(Accounting.QueryUserAccounting, client, user_id)
    usage_dict = {}
    util.SetIfNotNone(usage_dict, 'owned_by', _AccountingAsDict(owned))
    util.SetIfNotNone(usage_dict, 'shared_by', _AccountingAsDict(shared))
    util.SetIfNotNone(usage_dict, 'visible_to', _AccountingAsDict(visible))
    if len(usage_dict.keys()) > 0:
      # This notification may not have an inline field.
      last_notification = response['notifications'][-1]
      last_notification.setdefault('inline', {})['user'] = { 'usage': usage_dict }

  num_uploads = len([a for a in activities if a is not None and a.name == 'upload_episode'])
  num_shares = len([a for a in activities if a is not None and a.name in ['share_existing', 'share_new']])
  num_unshares = len([a for a in activities if a is not None and a.name == 'unshare'])
  num_comment_posts = len([a for a in activities if a is not None and a.name == 'post_comment'])
  logging.info('QUERY NOTIFICATIONS: user: %d, device: %d, start_key: %r, count: %d, '
               '%d uploads, %d shares, %d unshares, %d comment posts' %
               (user_id, device_id, start_key, len(notifications), num_uploads, num_shares,
                num_unshares, num_comment_posts))

  # Disable notification responses for older clients.
  if request['headers']['original_version'] < Message.UPDATE_SHARE_VERSION:
    raise gen.Return({'notifications': []})
  else:
    raise gen.Return(response)
dataset/ETHPy150Open viewfinderco/viewfinder/backend/www/service.py/QueryNotifications/original