def notify_posts(shard, post_list, sequence_numbers=None): """Notifies logged-in users of a set of new posts. Args: shard: Shard ID to notify for. post_list: When the post_list is a list of strings, then it's assumed these are the IDs of Posts that must be fetched prior to notification. Otherwise these should be Post entities. sequence_numbers: When supplied, should be a list of sequence numbers that correspond to each of the items in the post_list, in order. This is used to tell the user what the sequence ID of each post is within a particular shard. """ if not post_list: return if isinstance(post_list[0], basestring): post_keys = [ndb.Key(models.Post._get_kind(), post_id) for post_id in post_list] post_list = yield ndb.get_multi_async(post_keys) if not sequence_numbers: sequence_numbers = [None] * len(post_list) for post, sequence in zip(post_list, sequence_numbers): post.sequence = sequence posts_json = json.dumps({ 'posts': marshal_posts(shard, post_list), }) login_record_list = presence.get_present_users(shard) rpc_list = [] for login_record in login_record_list: logging.debug( 'Informing shard=%r, user=%r, nickname=%r about messages ' 'with sequence_numbers=%r', shard, login_record.user_id, login_record.nickname, sequence_numbers) browser_token = presence.get_token(login_record.user_id) rpc_list.append(send_message_async(browser_token, posts_json)) for rpc in rpc_list: try: yield rpc except channel.Error, e: # NOTE: When receiving an InvalidChannelKeyError the message may # still be available the next time the user connects to the channel # with that same application key due to buffering in the backends. # The dev_appserver mimics this behavior, but it's not reliable in # prod. logging.warning('Could not send JSON message to user=%r with ' 'browser_token=%r. %s: %s', login_record.user_id, browser_token, e.__class__.__name__, str(e))
def list_topics(root_shard_id, user_id): """Lists topics for a root shard and associated read state for the user. Args: root_shard_id: Shard ID of the root with associated topics. user_id: User ID that is requesting the list of topics and read states. Returns: Tuple (root_shard, shard_and_state_list) where: root_shard: Shard entity for the root. shard_and_state_list: List of pairs (Shard, ReadState) for associated topics and read states for the given user_id. Will be in order of update_time with most recently updated shards first. """ # TODO(bslatkin): Remove the root_shard return value. root_shard_future = models.Shard.get_by_id_async( root_shard_id, use_cache=False, use_memcache=False) oldest_update_time = ( datetime.datetime.now() - datetime.timedelta(seconds=config.ephemeral_lifetime_seconds)) query = models.Shard.query() query = query.filter(models.Shard.root_shard == root_shard_id) query = query.filter(models.Shard.update_time > oldest_update_time) query = query.order(-models.Shard.update_time) shard_list = yield query.fetch_async(100) # Include the root shard in the list of topics so the email digester # will include updates to the root shard if no topics have ever been sent. root_shard = yield root_shard_future shard_list.append(root_shard) # Get the current user's readstate for each shard that was found. read_state_key_list = [ ndb.Key(models.LoginRecord._get_kind(), user_id, models.ReadState._get_kind(), shard.shard_id) for shard in shard_list] read_state_list = yield ndb.get_multi_async(read_state_key_list) shard_and_state_list = zip(shard_list, read_state_list) raise ndb.Return((root_shard, shard_and_state_list))