Пример #1
0
  def _Check(self):
    """Gathers pre-mutation information:
       1. List of requested episodes and photos to save.
       2. Checkpoints list of episode and post ids that need to be (re)created.
       3. Acquires locks for all source viewpoints.

       Validates the following:
       1. Permissions to share from source episodes.
    """
    # Create save episode dicts from episodes and viewpoints in the request.
    self._save_ep_dicts = yield self._CreateSaveEpisodeDicts()

    # Validate episodes and posts to save.
    source_ep_posts_list = yield ViewfinderOperation._CheckCopySources('save',
                                                                       self._client,
                                                                       self._user.user_id,
                                                                       self._save_ep_dicts)

    # Get dicts describing the target episodes and posts.
    target_ep_ids = [ep_dict['new_episode_id'] for ep_dict in self._save_ep_dicts]
    self._target_ep_dicts = ViewfinderOperation._CreateCopyTargetDicts(self._op.timestamp,
                                                                       self._user.user_id,
                                                                       self._user.private_vp_id,
                                                                       source_ep_posts_list,
                                                                       target_ep_ids)

    # Lock all source viewpoints.
    for source_vp_id in set(episode.viewpoint_id for episode, posts in source_ep_posts_list):
      yield self._lock_tracker.AcquireViewpointLock(source_vp_id)

    # Start populating the checkpoint if this the first time the operation has been run.
    if self._op.checkpoint is None:
      # Get subset of target episodes and posts that need to be saved.
      self._new_ids = yield ViewfinderOperation._CheckCopyTargets('save',
                                                                  self._client,
                                                                  self._user.user_id,
                                                                  self._user.private_vp_id,
                                                                  self._target_ep_dicts)

      # Set checkpoint.
      # List of new episode/post ids need to be check-pointed because they may change in the
      # UPDATE phase. If we fail after UPDATE, but before NOTIFY, we would not send correct
      # notifications on retry.
      checkpoint = {'new': list(self._new_ids)}
      yield self._op.SetCheckpoint(self._client, checkpoint)
    else:
      # Restore state from checkpoint.
      self._new_ids = set(self._op.checkpoint['new'])

    raise gen.Return(True)
Пример #2
0
  def _AutoSave(self):
    """For each follower that has enabled auto-save for this viewpoint, trigger save_photos
    operation that will save the shared photos to their default viewpoint.
    """
    # Get ids of all the source episodes that will be provided to save_photos.
    source_ep_ids = [ep_dict['new_episode_id'] for ep_dict in self._ep_dicts]

    for follower in self._followers:
      # Skip follower if he did not mark this viewpoint for auto-saving.
      if not follower.ShouldAutoSave():
        continue

      # Skip follower if he is removed from the conversation.
      if follower.IsRemoved():
        continue

      follower_user = yield gen.Task(User.Query, self._client, follower.user_id, None)

      # Skip follower if he is the sharer, and is sharing only episodes from his default viewpoint.
      if follower_user.user_id == self._user_id:
        if all(source_episode.viewpoint_id == follower_user.private_vp_id
          for source_episode, posts in self._source_ep_posts_list):
            continue

      # Allocate ids for save_photos operation and activity.
      first_id = yield gen.Task(User.AllocateAssetIds, self._client, follower.user_id, 2)
      op_id = Operation.ConstructOperationId(follower_user.webapp_dev_id, first_id)
      activity_id = Activity.ConstructActivityId(self._act_dict['timestamp'],
                                                 follower_user.webapp_dev_id,
                                                 first_id + 1)

      # Generate ids for any target episodes that don't already exist.
      target_ep_ids = yield ViewfinderOperation._AllocateTargetEpisodeIds(self._client,
                                                                          follower.user_id,
                                                                          follower_user.webapp_dev_id,
                                                                          follower_user.private_vp_id,
                                                                          source_ep_ids)

      # Create target episode dicts expected by the SavePhotos op.
      target_eps_list = []
      for ep_dict, target_ep_id in zip(self._ep_dicts, target_ep_ids):
        target_eps_list.append({'existing_episode_id': ep_dict['new_episode_id'],
                                'new_episode_id': target_ep_id,
                                'photo_ids': ep_dict['photo_ids']})

      save_photos_dict = {'headers': {'op_id': op_id, 'op_timestamp': self._op.timestamp},
                          'user_id': follower.user_id,
                          'activity': {'activity_id': activity_id, 'timestamp': self._act_dict['timestamp']},
                          'episodes': target_eps_list}

      # Create the save_photos op for this user. Use the raw DBClient instance since self._client
      # is wrapped with OpMgrDBClient. 
      yield gen.Task(Operation.CreateAndExecute,
                     DBClient.Instance(),
                     follower.user_id,
                     follower_user.webapp_dev_id,
                     'SavePhotosOperation.Execute',
                     save_photos_dict)
Пример #3
0
    def _Check(self):
        """Gathers pre-mutation information:
       1. Existing viewpoint and owner follower.
       2. Followers of the existing viewpoint.
       3. List of requested episodes and photos to share.
       4. Checkpoints list of episode and post ids that need to be (re)created.
       5. Checkpoints list of followers that need to be revived.
       6. Checkpoints boolean indicating whether cover photo needs to be set.

       Validates the following:
       1. Permissions to share from source episodes.
       2. Permission to share into existing viewpoint.
    """
        self._viewpoint, self._follower = yield gen.Task(
            Viewpoint.QueryWithFollower, self._client, self._user_id,
            self._viewpoint_id)

        # Checks permission to share into viewpoint.
        if self._follower is None or not self._follower.CanContribute():
            raise PermissionError(
                'User %d does not have permission to contribute to viewpoint "%s".'
                % (self._user_id, self._viewpoint_id))
        assert self._viewpoint is not None, self._viewpoint_id

        # Get all existing followers.
        self._followers, _ = yield gen.Task(Viewpoint.QueryFollowers,
                                            self._client,
                                            self._viewpoint_id,
                                            limit=Viewpoint.MAX_FOLLOWERS)

        # Validate source episodes and posts and save the list for possible later use.
        self._source_ep_posts_list = yield ViewfinderOperation._CheckCopySources(
            'share', self._client, self._user_id, self._ep_dicts)

        # Get dicts describing the target episodes and posts.
        target_ep_ids = [
            ep_dict['new_episode_id'] for ep_dict in self._ep_dicts
        ]
        self._new_ep_dicts = ViewfinderOperation._CreateCopyTargetDicts(
            self._op.timestamp, self._user_id, self._viewpoint_id,
            self._source_ep_posts_list, target_ep_ids)

        # Start populating the checkpoint if this the first time the operation has been run.
        if self._op.checkpoint is None:
            # Get subset of target episodes and posts that need to be shared.
            self._new_ids = yield self._CheckCopyTargets(
                'share', self._client, self._user_id, self._viewpoint_id,
                self._new_ep_dicts)

            # Get list of followers which have removed themselves from the viewpoint and will need to be revived.
            self._revive_follower_ids = self._GetRevivableFollowers(
                self._followers)

            # Check whether a cover photo needs to be set on the viewpoint.
            self._need_cover_photo = not self._viewpoint.IsDefault(
            ) and not self._viewpoint.IsCoverPhotoSet()

            # Set checkpoint.
            # List of new episode/post ids and followers to revive need to be check-pointed because they may change
            # in the UPDATE phase. If we fail after UPDATE, but before NOTIFY, we would not send correct notifications
            # on retry.
            checkpoint = {
                'new': list(self._new_ids),
                'revive': self._revive_follower_ids,
                'cover': self._need_cover_photo
            }
            yield self._op.SetCheckpoint(self._client, checkpoint)
        else:
            # Restore state from checkpoint.
            self._new_ids = set(self._op.checkpoint['new'])
            self._revive_follower_ids = self._op.checkpoint['revive']
            self._need_cover_photo = self._op.checkpoint['cover']

        raise gen.Return(True)
Пример #4
0
    def _AutoSave(self):
        """For each follower that has enabled auto-save for this viewpoint, trigger save_photos
    operation that will save the shared photos to their default viewpoint.
    """
        # Get ids of all the source episodes that will be provided to save_photos.
        source_ep_ids = [
            ep_dict['new_episode_id'] for ep_dict in self._ep_dicts
        ]

        for follower in self._followers:
            # Skip follower if he did not mark this viewpoint for auto-saving.
            if not follower.ShouldAutoSave():
                continue

            # Skip follower if he is removed from the conversation.
            if follower.IsRemoved():
                continue

            follower_user = yield gen.Task(User.Query, self._client,
                                           follower.user_id, None)

            # Skip follower if he is the sharer, and is sharing only episodes from his default viewpoint.
            if follower_user.user_id == self._user_id:
                if all(source_episode.viewpoint_id ==
                       follower_user.private_vp_id for source_episode, posts in
                       self._source_ep_posts_list):
                    continue

            # Allocate ids for save_photos operation and activity.
            first_id = yield gen.Task(User.AllocateAssetIds, self._client,
                                      follower.user_id, 2)
            op_id = Operation.ConstructOperationId(follower_user.webapp_dev_id,
                                                   first_id)
            activity_id = Activity.ConstructActivityId(
                self._act_dict['timestamp'], follower_user.webapp_dev_id,
                first_id + 1)

            # Generate ids for any target episodes that don't already exist.
            target_ep_ids = yield ViewfinderOperation._AllocateTargetEpisodeIds(
                self._client, follower.user_id, follower_user.webapp_dev_id,
                follower_user.private_vp_id, source_ep_ids)

            # Create target episode dicts expected by the SavePhotos op.
            target_eps_list = []
            for ep_dict, target_ep_id in zip(self._ep_dicts, target_ep_ids):
                target_eps_list.append({
                    'existing_episode_id':
                    ep_dict['new_episode_id'],
                    'new_episode_id':
                    target_ep_id,
                    'photo_ids':
                    ep_dict['photo_ids']
                })

            save_photos_dict = {
                'headers': {
                    'op_id': op_id,
                    'op_timestamp': self._op.timestamp
                },
                'user_id': follower.user_id,
                'activity': {
                    'activity_id': activity_id,
                    'timestamp': self._act_dict['timestamp']
                },
                'episodes': target_eps_list
            }

            # Create the save_photos op for this user. Use the raw DBClient instance since self._client
            # is wrapped with OpMgrDBClient.
            yield gen.Task(Operation.CreateAndExecute, DBClient.Instance(),
                           follower.user_id, follower_user.webapp_dev_id,
                           'SavePhotosOperation.Execute', save_photos_dict)
Пример #5
0
  def _CreateSaveEpisodeDicts(self):
    """Creates a list of dicts describing the source and target episodes of the save. The
    episode dicts passed in the save_photos request are combined with episodes in any of the
    viewpoints passed in the save_photos request. Returns the list.
    """
    # Query episode ids from viewpoints given in the request, but skip those that are already in the request.
    vp_ep_ids = []
    skip_vp_ep_ids = set(ep_dict['existing_episode_id'] for ep_dict in self._ep_dicts)

    # Query photo_ids from viewpoint episodes.
    ep_ph_ids = {}

    @gen.coroutine
    def _VisitPosts(photo_ids, post):
      photo_ids.append(post.photo_id)

    @gen.coroutine
    def _VisitEpisodeKeys(episode_key):
      episode_id = episode_key.hash_key

      # Get list of episodes in the viewpoint that need a target episode id discovered/generated.
      if episode_id not in skip_vp_ep_ids:
        vp_ep_ids.append(episode_id)

      # For each episode in the viewpoint, get the complete list of photo ids in that episode.
      photo_ids = []
      yield gen.Task(Post.VisitRange, self._client, episode_id, None, None, partial(_VisitPosts, photo_ids))
      ep_ph_ids[episode_id] = photo_ids

    tasks = []
    for viewpoint_id in set(self._viewpoint_ids):
      query_expr = ('episode.viewpoint_id={id}', {'id': viewpoint_id})
      tasks.append(gen.Task(Episode.VisitIndexKeys, self._client, query_expr, _VisitEpisodeKeys))
    yield tasks

    # Allocate target ids for all episodes not given by the client.
    target_ep_ids = yield ViewfinderOperation._AllocateTargetEpisodeIds(self._client,
                                                                        self._user.user_id,
                                                                        self._user.webapp_dev_id,
                                                                        self._user.private_vp_id,
                                                                        vp_ep_ids)

    # Create save dicts for each of the viewpoint episodes to save.
    save_ep_dicts = {}
    for source_ep_id, target_ep_id in zip(vp_ep_ids, target_ep_ids):
      save_ep_dicts[target_ep_id] = {'existing_episode_id': source_ep_id,
                                     'new_episode_id': target_ep_id,
                                     'photo_ids': ep_ph_ids[source_ep_id]}

    # Now add the save dicts from the request, validating rules as we go.
    for ep_dict in self._ep_dicts:
      existing_ep_dict = save_ep_dicts.get(ep_dict['new_episode_id'], None)
      if existing_ep_dict is not None:
        if ep_dict['existing_episode_id'] != existing_ep_dict['existing_episode_id']:
          raise InvalidRequestError('Cannot save episodes "%s" and "%s" to same target episode "%s".' %
                                    (existing_ep_dict['existing_episode_id'],
                                     ep_dict['existing_episode_id'],
                                     ep_dict['new_episode_id']))

        existing_ep_dict['photo_ids'].extend(ep_dict['photo_ids'])
        existing_ep_dict['photo_ids'] = sorted(set(existing_ep_dict['photo_ids']))
      else:
        photo_ids = ep_dict['photo_ids']
        if ep_dict['existing_episode_id'] in ep_ph_ids:
          photo_ids.extend(ep_ph_ids[ep_dict['existing_episode_id']])

        save_ep_dicts[ep_dict['new_episode_id']] = {'existing_episode_id': ep_dict['existing_episode_id'],
                                                    'new_episode_id': ep_dict['new_episode_id'],
                                                    'photo_ids': sorted(set(photo_ids))}

    save_ep_dicts = [ep_dict for ep_dict in save_ep_dicts.itervalues()]
    save_ep_dicts.sort(key=itemgetter('new_episode_id'))
    raise gen.Return(save_ep_dicts)
Пример #6
0
    def _Check(self):
        """Gathers pre-mutation information:
       1. Checkpoints list of contacts that need to be made prospective users.
       2. Cover photo, if not specified.

       Validates the following:
       1. Max follower limit.
       2. Permissions to share from source episodes.
       3. Permission to create new viewpoint.
       4. Cover photo is contained in the request.

    Returns True if all checks succeeded and operation execution should continue, or False
    if the operation should end immediately.
    """
        if len(
                self._contact_dicts
        ) + 1 > Viewpoint.MAX_FOLLOWERS:  # +1 to account for user creating viewpoint.
            raise LimitExceededError(
                'User %d attempted to exceed follower limit on viewpoint "%s" by creating a viewpoint with %d followers.'
                % (self._user_id, self._viewpoint_id,
                   len(self._contact_dicts) + 1))

        # Validate source episodes and posts.
        source_ep_posts_list = yield ViewfinderOperation._CheckCopySources(
            'share', self._client, self._user_id, self._ep_dicts)

        # Get dicts describing the target episodes and posts.
        target_ep_ids = [
            ep_dict['new_episode_id'] for ep_dict in self._ep_dicts
        ]
        self._new_ep_dicts = ViewfinderOperation._CreateCopyTargetDicts(
            self._op.timestamp, self._user_id, self._viewpoint_id,
            source_ep_posts_list, target_ep_ids)

        # Does request explicitly set a cover photo?
        if self._vp_dict.has_key('cover_photo'):
            if self._vp_dict['type'] == Viewpoint.DEFAULT:
                # cover_photo isn't supported creating default viewpoint.
                raise InvalidRequestError(
                    'cover_photo is invalid in share_new request for default viewpoint.'
                )
            # Make sure the designated cover photo is contained in the request.
            elif not Viewpoint.IsCoverPhotoContainedInEpDicts(
                    self._vp_dict['cover_photo']['episode_id'],
                    self._vp_dict['cover_photo']['photo_id'],
                    self._new_ep_dicts):
                logging.warning(
                    'cover_photo is specified but not contained in request: vp_dict: %s, ep_dicts: %s',
                    self._vp_dict, self._ep_dicts)
                raise InvalidRequestError(
                    'cover_photo is specified but not contained in request.')
        else:
            # Select cover photo from the set being shared.
            self._vp_dict[
                'cover_photo'] = Viewpoint.SelectCoverPhotoFromEpDicts(
                    self._new_ep_dicts)

        # Start populating the checkpoint if this the first time the operation has been run.
        if self._op.checkpoint is None:
            # If viewpoint already exists, then just warn and do nothing. We do not raise an error
            # because sometimes the client resubmits the same operation with different ids.
            viewpoint = yield gen.Task(Viewpoint.Query,
                                       self._client,
                                       self._viewpoint_id,
                                       None,
                                       must_exist=False)
            if viewpoint is not None:
                logging.warning('target viewpoint "%s" already exists',
                                self._viewpoint_id)
                raise gen.Return(False)

            # Get a tuple for each contact: (user_exists?, user_id, webapp_dev_id).
            self._contact_ids = yield self._ResolveContactIds(
                self._contact_dicts)

            # Set checkpoint.
            # List of contacts need to be check-pointed because it may change in the UPDATE phase (when contacts
            # can be bound to prospective users). If we fail after UPDATE, but before NOTIFY, we would not send
            # correct notifications on retry.
            checkpoint = {'contacts': self._contact_ids}
            yield self._op.SetCheckpoint(self._client, checkpoint)
        else:
            # Restore state from checkpoint.
            self._contact_ids = self._op.checkpoint['contacts']

        self._contact_user_ids = [
            user_id
            for user_exists, user_id, webapp_dev_id in self._contact_ids
        ]

        raise gen.Return(True)
Пример #7
0
  def _Check(self):
    """Gathers pre-mutation information:
       1. Existing viewpoint and owner follower.
       2. Followers of the existing viewpoint.
       3. List of requested episodes and photos to share.
       4. Checkpoints list of episode and post ids that need to be (re)created.
       5. Checkpoints list of followers that need to be revived.
       6. Checkpoints boolean indicating whether cover photo needs to be set.

       Validates the following:
       1. Permissions to share from source episodes.
       2. Permission to share into existing viewpoint.
    """
    self._viewpoint, self._follower = yield gen.Task(Viewpoint.QueryWithFollower,
                                                     self._client,
                                                     self._user_id,
                                                     self._viewpoint_id)

    # Checks permission to share into viewpoint.
    if self._follower is None or not self._follower.CanContribute():
      raise PermissionError('User %d does not have permission to contribute to viewpoint "%s".' %
                            (self._user_id, self._viewpoint_id))
    assert self._viewpoint is not None, self._viewpoint_id

    # Get all existing followers.
    self._followers, _ = yield gen.Task(Viewpoint.QueryFollowers,
                                        self._client,
                                        self._viewpoint_id,
                                        limit=Viewpoint.MAX_FOLLOWERS)

    # Validate source episodes and posts and save the list for possible later use.
    self._source_ep_posts_list = yield ViewfinderOperation._CheckCopySources('share',
                                                                             self._client,
                                                                             self._user_id,
                                                                             self._ep_dicts)

    # Get dicts describing the target episodes and posts.
    target_ep_ids = [ep_dict['new_episode_id'] for ep_dict in self._ep_dicts]
    self._new_ep_dicts = ViewfinderOperation._CreateCopyTargetDicts(self._op.timestamp,
                                                                    self._user_id,
                                                                    self._viewpoint_id,
                                                                    self._source_ep_posts_list,
                                                                    target_ep_ids)

    # Start populating the checkpoint if this the first time the operation has been run.
    if self._op.checkpoint is None:
      # Get subset of target episodes and posts that need to be shared.
      self._new_ids = yield self._CheckCopyTargets('share',
                                                   self._client,
                                                   self._user_id,
                                                   self._viewpoint_id,
                                                   self._new_ep_dicts)

      # Get list of followers which have removed themselves from the viewpoint and will need to be revived.
      self._revive_follower_ids = self._GetRevivableFollowers(self._followers)

      # Check whether a cover photo needs to be set on the viewpoint.
      self._need_cover_photo = not self._viewpoint.IsDefault() and not self._viewpoint.IsCoverPhotoSet()

      # Set checkpoint.
      # List of new episode/post ids and followers to revive need to be check-pointed because they may change
      # in the UPDATE phase. If we fail after UPDATE, but before NOTIFY, we would not send correct notifications
      # on retry.
      checkpoint = {'new': list(self._new_ids),
                    'revive': self._revive_follower_ids,
                    'cover': self._need_cover_photo}
      yield self._op.SetCheckpoint(self._client, checkpoint)
    else:
      # Restore state from checkpoint.
      self._new_ids = set(self._op.checkpoint['new'])
      self._revive_follower_ids = self._op.checkpoint['revive']
      self._need_cover_photo = self._op.checkpoint['cover']

    raise gen.Return(True)
Пример #8
0
  def _Check(self):
    """Gathers pre-mutation information:
       1. Checkpoints list of contacts that need to be made prospective users.
       2. Cover photo, if not specified.

       Validates the following:
       1. Max follower limit.
       2. Permissions to share from source episodes.
       3. Permission to create new viewpoint.
       4. Cover photo is contained in the request.

    Returns True if all checks succeeded and operation execution should continue, or False
    if the operation should end immediately.
    """
    if len(self._contact_dicts) + 1 > Viewpoint.MAX_FOLLOWERS:   # +1 to account for user creating viewpoint.
      raise LimitExceededError(
          'User %d attempted to exceed follower limit on viewpoint "%s" by creating a viewpoint with %d followers.' %
          (self._user_id, self._viewpoint_id, len(self._contact_dicts) + 1))

    # Validate source episodes and posts.
    source_ep_posts_list = yield ViewfinderOperation._CheckCopySources('share',
                                                                       self._client,
                                                                       self._user_id,
                                                                       self._ep_dicts)

    # Get dicts describing the target episodes and posts.
    target_ep_ids = [ep_dict['new_episode_id'] for ep_dict in self._ep_dicts]
    self._new_ep_dicts = ViewfinderOperation._CreateCopyTargetDicts(self._op.timestamp,
                                                                    self._user_id,
                                                                    self._viewpoint_id,
                                                                    source_ep_posts_list,
                                                                    target_ep_ids)

    # Does request explicitly set a cover photo?
    if self._vp_dict.has_key('cover_photo'):
      if self._vp_dict['type'] == Viewpoint.DEFAULT:
        # cover_photo isn't supported creating default viewpoint.
        raise InvalidRequestError('cover_photo is invalid in share_new request for default viewpoint.')
      # Make sure the designated cover photo is contained in the request.
      elif not Viewpoint.IsCoverPhotoContainedInEpDicts(self._vp_dict['cover_photo']['episode_id'],
                                                        self._vp_dict['cover_photo']['photo_id'],
                                                        self._new_ep_dicts):
        logging.warning('cover_photo is specified but not contained in request: vp_dict: %s, ep_dicts: %s',
                        self._vp_dict,
                        self._ep_dicts)
        raise InvalidRequestError('cover_photo is specified but not contained in request.')
    else:
      # Select cover photo from the set being shared.
      self._vp_dict['cover_photo'] = Viewpoint.SelectCoverPhotoFromEpDicts(self._new_ep_dicts)

    # Start populating the checkpoint if this the first time the operation has been run.
    if self._op.checkpoint is None:
      # If viewpoint already exists, then just warn and do nothing. We do not raise an error
      # because sometimes the client resubmits the same operation with different ids.
      viewpoint = yield gen.Task(Viewpoint.Query, self._client, self._viewpoint_id, None, must_exist=False)
      if viewpoint is not None:
        logging.warning('target viewpoint "%s" already exists', self._viewpoint_id)
        raise gen.Return(False)

      # Get a tuple for each contact: (user_exists?, user_id, webapp_dev_id). 
      self._contact_ids = yield self._ResolveContactIds(self._contact_dicts)

      # Set checkpoint.
      # List of contacts need to be check-pointed because it may change in the UPDATE phase (when contacts
      # can be bound to prospective users). If we fail after UPDATE, but before NOTIFY, we would not send
      # correct notifications on retry.
      checkpoint = {'contacts': self._contact_ids}
      yield self._op.SetCheckpoint(self._client, checkpoint)
    else:
      # Restore state from checkpoint.
      self._contact_ids = self._op.checkpoint['contacts']

    self._contact_user_ids = [user_id for user_exists, user_id, webapp_dev_id in self._contact_ids]

    raise gen.Return(True)