#!/usr/bin/python
#
# Copyright 2009 Google Inc. All Rights Reserved.
#
# 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.

"""DocsClient extends gdata.client.GDClient to streamline DocList API calls."""


__author__ = 'e.bidelman (Eric Bidelman)'

import mimetypes
import urllib
import atom.data
import atom.http_core
import gdata.client
import gdata.docs.data
import gdata.gauth


# Feed URI templates
DOCLIST_FEED_URI = '/feeds/default/private/full/'
FOLDERS_FEED_TEMPLATE = DOCLIST_FEED_URI + '%s/contents'
ACL_FEED_TEMPLATE = DOCLIST_FEED_URI + '%s/acl'
REVISIONS_FEED_TEMPLATE = DOCLIST_FEED_URI + '%s/revisions'


class DocsClient(gdata.client.GDClient):
  """Client extension for the Google Documents List API."""

  host = 'docs.google.com'  # default server for the API
  api_version = '3.0'  # default major version for the service.
  auth_service = 'writely'
  auth_scopes = gdata.gauth.AUTH_SCOPES['writely']
  ssl = True

  def __init__(self, auth_token=None, **kwargs):
    """Constructs a new client for the DocList API.

    Args:
      auth_token: (optional) gdata.gauth.ClientLoginToken, AuthSubToken, or
          OAuthToken which authorizes this client to edit the user's data.
      kwargs: The other parameters to pass to gdata.client.GDClient constructor.
    """
    gdata.client.GDClient.__init__(self, auth_token=auth_token, **kwargs)

  def get_file_content(self, uri, auth_token=None, **kwargs):
    """Fetches the file content from the specified uri.

    This method is useful for downloading/exporting a file within enviornments
    like Google App Engine, where the user does not have the ability to write
    the file to a local disk.

    Args:
      uri: str The full URL to fetch the file contents from.
      auth_token: (optional) gdata.gauth.ClientLoginToken, AuthSubToken, or
          OAuthToken which authorizes this client to edit the user's data.
      kwargs: Other parameters to pass to self.request().

    Returns:
      The binary file content.

    Raises:
      gdata.client.RequestError: on error response from server.
    """
    server_response = self.request('GET', uri, auth_token=auth_token, **kwargs)
    if server_response.status != 200:
      raise  gdata.client.RequestError, {'status': server_response.status,
                                         'reason': server_response.reason,
                                         'body': server_response.read()}
    return server_response.read()

  GetFileContent = get_file_content

  def _download_file(self, uri, file_path, auth_token=None, **kwargs):
    """Downloads a file to disk from the specified URI.

    Note: to download a file in memory, use the GetFileContent() method.

    Args:
      uri: str The full URL to download the file from.
      file_path: str The full path to save the file to.
      auth_token: (optional) gdata.gauth.ClientLoginToken, AuthSubToken, or
          OAuthToken which authorizes this client to edit the user's data.
      kwargs: Other parameters to pass to self.get_file_content().

    Raises:
      gdata.client.RequestError: on error response from server.
    """
    f = open(file_path, 'wb')
    try:
      f.write(self.get_file_content(uri, auth_token=auth_token, **kwargs))
    except gdata.client.RequestError, e:
      f.close()
      raise e
    f.flush()
    f.close()

  _DownloadFile = _download_file

  def get_doclist(self, uri=None, limit=None, auth_token=None, **kwargs):
    """Retrieves the main doclist feed containing the user's items.

    Args:
      uri: str (optional) A URI to query the doclist fee
d.
      limit: int (optional) A maximum cap for the number of results to
          return in the feed. B
y default, the API returns a maximum of 100
          per page. Thus, if you set limit=5000, you will get <= 5000
          documents (guarenteed no more than 5000), and will need to follow the
          feed's next links (feed.GetNextLink()) to the rest. See
          get_everything(). Similarly, if you set limit=50, only <= 50
          documents are returned. Note: if the max-results parameter is set in
          the uri parameter, it is chosen over a value set for limit.
      auth_token: (optional) gdata.gauth.ClientLoginToken, AuthSubToken, or
          OAuthToken which authorizes this client to edit the user's data.
      kwargs: Other parameters to pass to self.get_feed().

    Returns:
      gdata.docs.data.DocList feed.
    """
    if uri is None:
      uri = DOCLIST_FEED_URI

    if isinstance(uri, (str, unicode)):
      uri = atom.http_core.Uri.parse_uri(uri)    

    # Add max-results param if it wasn't included in the uri.
    if limit is not None and not 'max-results' in uri.query:
      uri.query['max-results'] = limit

    return self.get_feed(uri, desired_class=gdata.docs.data.DocList,
                         auth_token=auth_token, **kwargs)

  GetDocList = get_doclist

  def get_doc(self, resource_id, etag=None, auth_token=None, **kwargs):
    """Retrieves a particular document given by its resource id.

    Args:
      resource_id: str The document/item's resource id. Example spreadsheet:
          'spreadsheet%3A0A1234567890'.
      etag: str (optional) The document/item's etag value to be used in a
          conditional GET. See http://code.google.com/apis/documents/docs/3.0/
          developers_guide_protocol.html#RetrievingCached.
      auth_token: (optional) gdata.gauth.ClientLoginToken, AuthSubToken, or
          OAuthToken which authorizes this client to edit the user's data.
      kwargs: Other parameters to pass to self.get_entry().

    Returns:
      A gdata.docs.data.DocsEntry object representing the retrieved entry.

    Raises:
      ValueError if the resource_id is not a valid format.
    """
    match = gdata.docs.data.RESOURCE_ID_PATTERN.match(resource_id)
    if match is None:
      raise ValueError, 'Invalid resource id: %s' % resource_id
    return self.get_entry(
        DOCLIST_FEED_URI + resource_id, etag=etag,
        desired_class=gdata.docs.data.DocsEntry,
        auth_token=auth_token, **kwargs)

  GetDoc = get_doc

  def get_everything(self, uri=None, auth_token=None, **kwargs):
    """Retrieves the user's entire doc list.

    The method makes multiple HTTP requests (by following the feed's next links)
    in order to fetch the user's entire document list.

    Args:
      uri: str (optional) A URI to query the doclist feed with.
      auth_token: (optional) gdata.gauth.ClientLoginToken, AuthSubToken, or
          OAuthToken which authorizes this client to edit the user's data.
      kwargs: Other parameters to pass to self.GetDocList().

    Returns:
      A list of gdata.docs.data.DocsEntry objects representing the retrieved
      entries.
    """
    if uri is None:
      uri = DOCLIST_FEED_URI

    feed = self.GetDocList(uri=uri, auth_token=auth_token, **kwargs)
    entries = feed.entry

    while feed.GetNextLink() is not None:
      feed = self.GetDocList(
          feed.GetNextLink().href, auth_token=auth_token, **kwargs)
      entries.extend(feed.entry)

    return entries

  GetEverything = get_everything

  def get_acl_permissions(self, resource_id, auth_token=None, **kwargs):
    """Retrieves a the ACL sharing permissions for a document.

    Args:
      resource_id: str The document/item's resource id. Example for pdf:
          'pdf%3A0A1234567890'.
      auth_token: (optional) gdata.gauth.ClientLoginToken, AuthSubToken, or
          OAuthToken which authorizes this client to edit the user's data.
      kwargs: Other parameters to pass to self.get_feed().

    Returns:
      A gdata.docs.data.AclFeed object representing the document's ACL entries.

    Raises:
      Va