#!/usr/bin/python
"""ubuntuone-couchdb-query: Command line query tool for Ubuntu One CouchDBs"""

# sil@kryogenix.org, 2010-02-13

from optparse import OptionParser
from oauth import oauth
import gnomekeyring, gobject, httplib2, simplejson, urlparse, cgi, urllib
import socket
socket.setdefaulttimeout(5)

def get_prod_oauth_token():
    """Get the token from the keyring"""
    gobject.set_application_name("Ubuntu One Web API Tool")

    consumer = oauth.OAuthConsumer("ubuntuone", "hammertime")
    items = []
    items = gnomekeyring.find_items_sync(
        gnomekeyring.ITEM_GENERIC_SECRET,
        {'ubuntuone-realm': "https://ubuntuone.com",
         'oauth-consumer-key': consumer.key})
    return oauth.OAuthToken.from_string(items[0].secret)

def get_oauth_request_header(consumer, access_token, http_url, signature_method):
    """Get an oauth request header given the token and the url"""
    assert http_url.startswith("https")
    oauth_request = oauth.OAuthRequest.from_consumer_and_token(
        http_url=http_url,
        http_method="GET",
        oauth_consumer=consumer,
        token=access_token)
    oauth_request.sign_request(signature_method, consumer, access_token)
    return oauth_request.to_header()

def request(urlpath, sigmeth, http_method, request_body, show_tokens):
    """Make a request to couchdb.one.ubuntu.com for the user's data.
    
       The user supplies a urlpath (for example, dbname). We need to actually
       request https://couchdb.one.ubuntu.com/PREFIX/dbname, and sign it with
       the user's OAuth token, which must be in the keyring.
       
       We find the prefix by querying https://one.ubuntu.com/api/account/
       (see desktopcouch.replication_services.ubuntuone, which does this).
    """
    
    # First check that there's a token in the keyring.
    try:
        access_token = get_prod_oauth_token()
    except: # bare except, norty
        raise Exception("Unable to retrieve your Ubuntu One access details "
            "from the keyring. Try connecting your machine to Ubuntu One.")
    
    # Set the signature method. This should be HMAC unless you have a jolly
    # good reason for it to not be.
    if sigmeth == "PLAINTEXT":
        signature_method = oauth.OAuthSignatureMethod_PLAINTEXT()
    elif sigmeth == "HMAC_SHA1":
        signature_method = oauth.OAuthSignatureMethod_HMAC_SHA1()
    else:
        signature_method = oauth.OAuthSignatureMethod_PLAINTEXT()
    
    # Create the Ubuntu One OAuth consumer for all requests
    consumer = oauth.OAuthConsumer("ubuntuone", "hammertime")
    
    if show_tokens:
        print "Using OAuth data:"
        print "consumer: %s : %s\ntoken: %s" % (
            consumer.key, consumer.secret, access_token)
    
    # Look up the user's prefix
    infourl = "https://one.ubuntu.com/api/account/"
    oauth_header = get_oauth_request_header(consumer, access_token, infourl, signature_method)
    client = httplib2.Http()
    resp, content = client.request(infourl, "GET", headers=oauth_header)
    if resp['status'] == "200":
        try:
            document = simplejson.loads(content)
        except ValueError:
            raise Exception("Got unexpected content:\n%s" % content)
        if "couchdb_root" not in document:
            raise ValueError("couchdb_root not found in %s" % (document,))
        COUCH_ROOT = document["couchdb_root"]
    else:
        raise ValueError("Error retrieving user data (%s)" % (resp['status'], 
            url))

    # COUCH_ROOT must have all internal slashes escaped
    schema, netloc, path, params, query, fragment = urlparse.urlparse(COUCH_ROOT)
    path = "/" + urllib.quote(path[1:], safe="") # don't escape the first /
    COUCH_ROOT = urlparse.urlunparse((schema, netloc, path, params, query, fragment))
    
    # Now use COUCH_ROOT and the specified user urlpath to get data
    if urlpath == "_all_dbs":
        couch_url = "%s%s" % (COUCH_ROOT, urlpath)
    else:
        couch_url = "%s%%2F%s" % (COUCH_ROOT, urlpath)
    schema, netloc, path, params, query, fragment = urlparse.urlparse(couch_url)
    querystr_as_dict = dict(cgi.parse_qsl(query))
    oauth_request = oauth.OAuthRequest.from_consumer_and_token(
        http_url=couch_url,
        http_method=http_method,
        oauth_consumer=consumer,
        token=access_token,
        parameters=querystr_as_dict)
    oauth_request.sign_request(signature_method, consumer, access_token)
    failed = 0
    while 1:
        try:
            resp, content = client.request(couch_url, http_method, 
                headers=oauth_request.to_header(), body=request_body)
            break
        except IOError:
            failed += 1
    if failed > 0:
        if failed == 1:
            s = ""
        else:
            s = "s"
        print "(request failed %s time%s)" % (failed, s)
    if resp['status'] == "200":
        try:
            return simplejson.loads(content)
        except:
            print "(data returned from CouchDB was invalid JSON)"
            return content
    elif resp['status'] == "400":
        print "The server could not parse the oauth token:\n%s" % content
    elif resp['status'] == "401":
        print "Access Denied"
        print "Content:"
        return content
    else:
        return (
            "There was a problem processing the request:\nstatus:%s, response:"
            " %r" % (resp['status'], content))


if __name__ == "__main__":
    parser = OptionParser(usage="prog [options] urlpath")
    parser.add_option("--oauth-signature-method", dest="sigmeth",
                      default="HMAC_SHA1",
                      help="OAuth signature method to use (PLAINTEXT or "
                      "HMAC_SHA1)")
    parser.add_option("--http-method", dest="http_method",
                      default="GET",
                      help="HTTP method to use")
    parser.add_option("--body", dest="body",
                      default=None,
                      help="HTTP request body")
    parser.add_option("--show-tokens", dest="show_tokens",
                      default=False,
                      help="Show the OAuth tokens we're using")

    (options, args) = parser.parse_args()
    if len(args) != 1:
        parser.error("You must specify a urlpath (e.g., a dbname)")
    print request(
        urlpath=args[0], sigmeth=options.sigmeth,
        http_method=options.http_method, request_body=options.body,
        show_tokens=options.show_tokens)
