#!/usr/bin/env python
#
# Copyright 2007 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#




"""Used to confirm and act on delete requests from the Admin Console."""





import os
import re
import urllib

from google.appengine.api import capabilities
from google.appengine.api import datastore
from google.appengine.ext import webapp
from google.appengine.ext.datastore_admin import config
from google.appengine.ext.datastore_admin import utils


try:

  from google.appengine.ext.mapreduce import model
  from google.appengine.ext.mapreduce import input_readers
  from google.appengine.ext.mapreduce import operation
except ImportError:

  from google.appengine._internal.mapreduce import model
  from google.appengine._internal.mapreduce import input_readers
  from google.appengine._internal.mapreduce import operation


MAPREDUCE_OBJECTS = [model.MapreduceState.kind(),
                     model.ShardState.kind()]
XSRF_ACTION = 'delete'
KIND_AND_SIZE_RE = re.compile('^(.*)\|(-?[0-9]+)$')


def DeleteEntity(key):
  """Delete function which deletes all processed entities.

  Args:
    key: key of the entity to delete.

  Yields:
    a delete operation if the entity is not an active mapreduce or
    DatastoreAdminOperation object.
  """
  if key.kind() in MAPREDUCE_OBJECTS:
    entity = datastore.Get(key)
    if entity and not entity["active"]:
      yield operation.db.Delete(key)
  elif key.kind() == utils.DatastoreAdminOperation.kind():
    entity = datastore.Get(key)
    if entity and not entity["active_jobs"]:
      yield operation.db.Delete(key)
  else:
    yield operation.db.Delete(key)


class ConfirmDeleteHandler(webapp.RequestHandler):
  """Handler to deal with requests from the admin console to delete data."""

  SUFFIX = 'confirm_delete'

  @classmethod
  def Render(cls, handler):
    """Rendering method that can be called by main.py or get.

    This method executes no action, so the method by which it is accessed is
    immaterial.  Creating a form with get may be a desirable function.  That is,
    if this builtin is turned on, anyone can create a form to delete a kind by
    simply linking to the ConfirmDeleteHandler like so:
    <a href="/_ah/datastore_admin/confirm_delete?kind=trash">
        Delete all Trash Objects</a>

    Args:
      handler: the webapp.RequestHandler invoking the method
    """
    readonly_warning = not capabilities.CapabilitySet(
        'datastore_v3', capabilities=['write']).is_enabled()
    namespace = handler.request.get('namespace')
    kinds = handler.request.get_all('kind')
    sizes_known, size_total, remainder = utils.ParseKindsAndSizes(kinds)

    (namespace_str, kind_str) = utils.GetPrintableStrs(namespace, kinds)
    template_params = {
        'readonly_warning': readonly_warning,
        'form_target': DoDeleteHandler.SUFFIX,
        'kind_list': kinds,
        'remainder': remainder,
        'sizes_known': sizes_known,
        'size_total': size_total,
        'app_id': handler.request.get('app_id'),
        'datastore_admin_home': utils.GenerateHomeUrl(handler.request),
        'kind_str': kind_str,
        'namespace_str': namespace_str,
        'xsrf_token': utils.CreateXsrfToken(XSRF_ACTION),
    }
    utils.RenderToResponse(handler, 'confirm_delete.html', template_params)

  def get(self):
    """Handler for get requests to datastore_admin/confirm_delete."""
    ConfirmDeleteHandler.Render(self)


class DoDeleteHandler(webapp.RequestHandler):
  """Handler to deal with requests from the admin console to delete data."""

  SUFFIX = 'delete.do'
  DELETE_HANDLER = (
      'google.appengine.ext.datastore_admin.delete_handler.DeleteEntity')
  INPUT_READER = input_readers.__name__ + '.DatastoreKeyInputReader'
  MAPREDUCE_DETAIL = config.MAPREDUCE_PATH + '/detail?mapreduce_id='

  def get(self):
    """Handler for get requests to datastore_admin/delete.do.

    Status of executed jobs is displayed.
    """
    jobs = self.request.get_all('job')
    error = self.request.get('error', '')
    xsrf_error = self.request.get('xsrf_error', '')
    noconfirm_error = self.request.get('noconfirm_error', '')

    template_params = {
        'job_list': jobs,
        'mapreduce_detail': self.MAPREDUCE_DETAIL,
        'error': error,
        'xsrf_error': xsrf_error,
        'noconfirm_error': noconfirm_error,
        'datastore_admin_home': config.BASE_PATH,
    }
    utils.RenderToResponse(self, 'do_delete.html', template_params)

  def post(self):
    """Handler for post requests to datastore_admin/delete.do.

    Jobs are executed and user is redirected to the get handler.
    """
    namespace = self.request.get('namespace')
    kinds = self.request.get_all('kind')
    (namespace_str, kinds_str) = utils.GetPrintableStrs(namespace, kinds)
    token = self.request.get('xsrf_token')
    readonly_warning = self.request.get('readonly_warning')

    jobs = []

    if (readonly_warning == 'True') and not self.request.get(
        'confirm_readonly_delete'):
      parameters = [('noconfirm_error', '1')]
    else:
      if utils.ValidateXsrfToken(token, XSRF_ACTION):
        try:
          op = utils.StartOperation(
              'Deleting %s%s' % (kinds_str, namespace_str))
          name_template = 'Delete all %(kind)s objects%(namespace)s'
          mapreduce_params = {'force_ops_writes': True}
          queue = self.request.get('queue')
          queue = queue or os.environ.get(
              'HTTP_X_APPENGINE_QUEUENAME', 'default')
          if queue[0] == '_':

            queue = 'default'
          jobs = utils.RunMapForKinds(
              op.key(),
              kinds,
              name_template,
              self.DELETE_HANDLER,
              self.INPUT_READER,
              None,
              {},
              mapreduce_params=mapreduce_params,
              queue_name=queue,
              max_shard_count=utils.MAPREDUCE_DEFAULT_SHARDS)
          error = ''


        except Exception, e:
          error = self._HandleException(e)

        parameters = [('job', job) for job in jobs]
        if error:
          parameters.append(('error', error))
      else:
        parameters = [('xsrf_error', '1')]

    query = urllib.urlencode(parameters)

    self.redirect('%s/%s?%s' % (config.BASE_PATH, self.SUFFIX, query))

  def _HandleException(self, e):
    """Make exception handling overrideable by tests.

    In normal cases, return only the error string; do not fail to render the
    page for user.
    """
    return str(e)
