#!/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.
#




"""A Python blobstore API used by app developers.

Contains methods used to interface with Blobstore API.  Includes db.Model-like
class representing a reference to a very large BLOB.  Imports db.Key-like
class representing a blob-key.
"""







import cgi
import email
import os

from google.appengine.api import datastore
from google.appengine.api import datastore_errors
from google.appengine.api import datastore_types
from google.appengine.api.blobstore import blobstore
from google.appengine.ext import db

__all__ = ['BLOB_INFO_KIND',
           'BLOB_KEY_HEADER',
           'BLOB_RANGE_HEADER',
           'BlobFetchSizeTooLargeError',
           'BlobInfo',
           'BlobInfoParseError',
           'BlobKey',
           'BlobNotFoundError',
           'BlobReferenceProperty',
           'BlobReader',
           'DataIndexOutOfRangeError',
           'Error',
           'InternalError',
           'MAX_BLOB_FETCH_SIZE',
           'UPLOAD_INFO_CREATION_HEADER',
           'create_upload_url',
           'delete',
           'fetch_data',
           'get',
           'parse_blob_info']

Error = blobstore.Error
InternalError = blobstore.InternalError
BlobFetchSizeTooLargeError = blobstore.BlobFetchSizeTooLargeError
BlobNotFoundError = blobstore.BlobNotFoundError
_CreationFormatError = blobstore._CreationFormatError
DataIndexOutOfRangeError = blobstore.DataIndexOutOfRangeError

BlobKey = blobstore.BlobKey
create_upload_url = blobstore.create_upload_url
delete = blobstore.delete


class BlobInfoParseError(Error):
  """CGI parameter does not contain valid BlobInfo record."""


BLOB_INFO_KIND = blobstore.BLOB_INFO_KIND
BLOB_KEY_HEADER = blobstore.BLOB_KEY_HEADER
BLOB_RANGE_HEADER = blobstore.BLOB_RANGE_HEADER
MAX_BLOB_FETCH_SIZE = blobstore.MAX_BLOB_FETCH_SIZE
UPLOAD_INFO_CREATION_HEADER = blobstore.UPLOAD_INFO_CREATION_HEADER



class _GqlQuery(db.GqlQuery):
  """GqlQuery class that explicitly sets model-class.

  This does the same as the original db.GqlQuery class except that it does
  not try to find the model class based on the compiled GQL query.  The
  caller instead provides the query with a model class to use for construction.

  This class is required for compatibility with the current db.py query
  mechanism but will be removed in the future.  DO NOT USE.
  """


  def __init__(self, query_string, model_class, *args, **kwds):
    """Constructor.

    Args:
      query_string: Properly formatted GQL query string.
      model_class: Model class from which entities are constructed.
      *args: Positional arguments used to bind numeric references in the query.
      **kwds: Dictionary-based arguments for named references.
    """


    from google.appengine.ext import gql
    app = kwds.pop('_app', None)
    self._proto_query = gql.GQL(query_string, _app=app, namespace='')

    super(db.GqlQuery, self).__init__(model_class, namespace='')
    self.bind(*args, **kwds)




class BlobInfo(object):
  """Information about blobs in Blobstore.

  This is a db.Model-like class that contains information about blobs stored
  by an application.  Like db.Model, this class is backed by an Datastore
  entity, however, BlobInfo instances are read-only and have a much more
  limited interface.

  Each BlobInfo has a key of type BlobKey associated with it. This key is
  specific to the Blobstore API and is not compatible with db.get.  The key
  can be used for quick lookup by passing it to BlobInfo.get.  This
  key converts easily to a string, which is web safe and can be embedded
  in URLs.

  Properties:
    content_type: Content type of blob.
    creation: Creation date of blob, when it was uploaded.
    filename: Filename user selected from their machine.
    size: Size of uncompressed blob.

  All properties are read-only.  Attempting to assign a value to a property
  will raise NotImplementedError.
  """

  _unindexed_properties = frozenset()

  @property
  def content_type(self):
    return self.__get_value('content_type')

  @property
  def creation(self):
    return self.__get_value('creation')

  @property
  def filename(self):
    return self.__get_value('filename')

  @property
  def size(self):
    return self.__get_value('size')

  def __init__(self, entity_or_blob_key, _values=None):
    """Constructor for wrapping blobstore entity.

    The constructor should not be used outside this package and tests.

    Args:
      entity: Datastore entity that represents the blob reference.
    """
    if isinstance(entity_or_blob_key, datastore.Entity):
      self.__entity = entity_or_blob_key
      self.__key = BlobKey(entity_or_blob_key.key().name())
    elif isinstance(entity_or_blob_key, BlobKey):
      self.__entity = _values
      self.__key = entity_or_blob_key
    else:
      TypeError('Must provide Entity or BlobKey')



  @classmethod
  def from_entity(cls, entity):
    """Convert entity to BlobInfo.

    This method is required for compatibility with the current db.py query
    mechanism but will be removed in the future.  DO NOT USE.
    """
    return BlobInfo(entity)



  @classmethod
  def properties(cls):
    """Set of properties that belong to BlobInfo.

    This method is required for compatibility with the current db.py query
    mechanism but will be removed in the future.  DO NOT USE.
    """
    return set(('content_type', 'creation', 'filename', 'size'))

  def __get_value(self, name):
    """Get a BlobInfo value, loading entity if necessary.

    This method allows lazy loading of the underlying datastore entity.  It
    should never be invoked directly.

    Args:
      name: Name of property to get value for.

    Returns:
      Value of BlobInfo property from entity.
    """
    if self.__entity is None:
      self.__entity = datastore.Get(
          datastore_types.Key.from_path(
              self.kind(), str(self.__key), namespace=''))
    try:
      return self.__entity[name]
    except KeyError:
      raise AttributeError(name)


  def key(self):
    """Get key for blob.

    Returns:
      BlobKey instance that identifies this blob.
    """
    return self.__key

  def delete(self):
    """Permanently delete blob from Blobstore."""
    delete(self.key())

  def open(self, *args, **kwargs):
    """Returns a BlobReader for this blob.

    Args:
      *args, **kwargs: Passed to BlobReader constructor.
    Returns:
      A BlobReader instance.
    """
    return BlobReader(self, *args, **kwargs)

  @classmethod
  def get(cls, blob_keys):