#!/usr/bin/python
usage = """
cush - Cloud User Shell multi-call executable 1.0 $Rev$
http://code.google.com/p/cush

Copyright (C) 2008 Sam Johnston, Australian Online Solutions Pty Ltd
$Id$

This program is free software: you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
Foundation, either version 3 of the License, or (at your option) any later
version.
 
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
details.
 
You should have received a copy of the GNU General Public License along with
this program.  If not, see <http://www.gnu.org/licenses/>.

Usage: cush [verb] [noun]
   or: [verb] [noun]
   eg: get http://samj.net

Cloud User SHell (cush) is a multi-call binary (like BusyBox) that combines
many useful cloud computing utilities into a single executable. Most people
will create a link to cush for each function they wish to use and cush will
act like whatever it was invoked as, but it can also be called directly and
passed the command as the first parameter.

Currently defined functions:
%s

Report issues and enhancements at http://code.google.com/p/cush/issues/entry
"""
import os
import sys
import urllib2
import httplib
from urlparse import urlparse

USERAGENT = "cush/0.1"
PASSTHRU  = ('head', 'ls', 'rm')
(WARNVER, REQMAJ, REQMIN) = (True, 2, 5)
DEBUG     = 0
PATH      = '/usr/bin:/bin:/usr/sbin:/sbin'
try:
	SELF      = "$Id$".split()[1]
	HEAD      = "$HeadURL$".split()[1]
	REV       = "$Rev$".split()[1]
except:
	# set sensible defaults in case the substitutions are reduced (eg $Rev$ rather than $Rev$)
	(SELF, HEAD, REV) = ('cush.py', 'http://cush.googlecode.com/svn/trunk/src/python/cush.py', 'unknown')
# disable HTTPS to avoid unnecessary authorisation
if HEAD[:5] == 'https': HEAD = 'http' + HEAD[5:]

httplib.HTTPConnection.debuglevel = DEBUG
urllib2.install_opener(urllib2.build_opener(urllib2.HTTPHandler(debuglevel=DEBUG)))

def do_usage ():
	print usage % ', '.join(commands.keys())

def do_unknown (task='do that'):
	do_usage()
	print "Sorry, I don't know how to %s yet." % task
	print "Why not come to http://code.google.com/p/cush/ and teach me?"

def do_get (url):
	data = None
	if not sys.stdin.isatty(): data = sys.stdin.read()
	try:
		request = urllib2.Request(url, data, { 'User-Agent': USERAGENT })
		if sys.argv[0][-2:] == '@@':
			print urllib2.urlopen(request).info()
		print urllib2.urlopen(request).read()
	except urllib2.HTTPError, ex:
	    print 'Sorry, something went wrong with the server: ', ex.code
	except urllib2.URLError, ex:
	    print 'Sorry, I failed to reach a server and have given up: ', ex.reason
	
def do_head (url):
	purl = urlparse(url)
	conn = httplib.HTTPConnection(purl.hostname, purl.port)
	conn.putrequest('HEAD', purl.path)
	conn.putheader('User-Agent', USERAGENT)
	conn.endheaders()
	resp = conn.getresponse()
	headers = resp.getheaders()
	if not 200 <= resp.status <= 299:
		exit("%d %s" % (resp.status, resp.reason))
	for header in headers:
		print ': '.join(header)

def do_install ():
	if sys.platform == 'win32':
		print """Nothing to be done for Windows as it doesn't support symlinks or aliases.
We are looking for integration ideas but develop on, use (and recommend) 
Mac and Linux. In the mean time use: cush.pl [verb] [noun]
For example: cush.pl get http://samj.net/
Suggest improvements at http://code.google.com/p/cush/issues/detail?id=22"""
	else:
		print "Installing..."
		for c in commands.keys():
			if not os.path.lexists(c):
				os.symlink(SELF, c)
				print ' linked:',
			else:
				print ' exists:',
			print "%s" % c
		print "Done."

def do_update ():
	print "Attempting to retrieve %s from %s:" % (SELF, HEAD)
	os.system("/usr/bin/env svn export %s %s.new" % (HEAD, SELF))
	if not os.path.exists(SELF + '.new'):
		print "Falling back to internal update mechanism as there was a problem with svn"
		file = open(SELF + ".new", "w")
		file.write(urllib2.urlopen(HEAD).read())
		file.close()
	# carefully move new version into place
	if os.path.exists(SELF + '.new'):
		print "Saving permissions of %s" % SELF
		mode = os.stat(SELF).st_mode
		print "Backing up %s to %s.r%s" % (SELF, SELF, REV)
		os.rename(SELF, SELF + ".r%s" % REV)
		print "Renaming %s.new to %s" % (SELF, SELF)
		os.rename(SELF + '.new', SELF)
		print "Restoring permissions of %s" % SELF
		os.chmod(SELF, mode)
	else:
		sys.exit("Update failed. Please report at http://code.google.com/p/cush/issues/entry")
	print "Update successful."

def do_uninstall ():
	if sys.platform == 'win32':
		print """Nothing to do for Windows (yet). If this upsets you tell us about it at
http://code.google.com/p/cush/issues/detail?id=22"""
	else:
		print "Uninstalling..."
		for c in commands.keys():
			if os.path.lexists(c):
		 		if os.path.basename(os.readlink(c)):
					os.remove(c)
					print ' removed: %s' % c
			else:
				print ' missing: %s' % c
		print "Done."

def do_options (url):
	purl = urlparse(url)
	conn = httplib.HTTPConnection(purl.hostname, purl.port)
	conn.putrequest('OPTIONS', purl.path)
	conn.putheader('User-Agent', USERAGENT)
	conn.endheaders()
	resp = conn.getresponse()
	headers = resp.getheaders()
	if not 200 <= resp.status <= 299:
		exit("%d %s" % (resp.status, resp.reason))
	for header in headers:
		print ': '.join(header)

def do_put (url):
	purl = urlparse(url)
	conn = httplib.HTTPConnection(purl.hostname, purl.port)
	conn.putrequest('PUT', purl.path)
	conn.putheader('Content-Type', 'text/plain')
	conn.putheader('Connection', 'keep-alive')
	conn.putheader('Transfer-Encoding', 'chunked')
	conn.putheader('User-Agent', USERAGENT)
	conn.putheader('Accept', '*/*')
	conn.endheaders()
	while True:
		chunk = sys.stdin.read(2048)
		if not chunk: break
		length = len(chunk)
		conn.send('%X\r\n' % length)
		conn.send(chunk + '\r\n')
	conn.send('0\r\n\r\n')
	resp = conn.getresponse()
	if not 200 <= resp.status <= 299:
		exit("%d %s" % (resp.status, resp.reason))

def do_delete (url):
	purl = urlparse(url)
	conn = httplib.HTTPConnection(purl.hostname, purl.port)
	conn.putrequest('DELETE', purl.path)
	conn.putheader('User-Agent', USERAGENT)
	conn.endheaders()
	resp = conn.getresponse()
	if not 200 <= resp.status <= 299:
		exit("%d %s" % (resp.status, resp.reason))

def do_propfind (url):
	purl = urlparse(url)
	conn = httplib.HTTPConnection(purl.hostname, purl.port)
	conn.putrequest('PROPFIND', purl.path)
	conn.putheader('User-Agent', USERAGENT)
	conn.putheader('Depth', 1)
	conn.endheaders()
	resp = conn.getresponse()
	if not 200 <= resp.status <= 299:
		exit("%d %s" % (resp.status, resp.reason))
	# rudimentary directory listing using PROPFIND
	if command == 'ls':
		import xml.dom.minidom
		for node in xml.dom.minidom.parseString(resp.read()).getElementsByTagName("D:href"):
			if node.firstChild.data != purl.path:
				print node.firstChild.data[len(purl.path):]
	else:
		print resp.read()

def do_trace (url):
	purl = urlparse(url)
	conn = httplib.HTTPConnection(purl.hostname, purl.port)
	conn.putrequest('TRACE', purl.path)
	conn.putheader('Content-Type', 'message/http')
	conn.putheader('User-Agent', USERAGENT)
	conn.putheader('Max-Forwards', 64)
	conn.endheaders()
	resp = conn.getresponse()
	headers = resp.getheaders()
	if not 200 <= resp.status <= 299:
		exit("%d %s" % (resp.status, resp.reason))
	for header in headers:
		print ': '.join(header)

command = do_unknown
commands = {
	"@": do_get,
	"@@": do_get,
	"delete": do_delete,
	"get": do_get,
	"head": do_head,
	"install": do_install,
	"ls": do_propfind,
	"options": do_options,
	"post": do_get, # with urllib2 if data is provided it switches from GET to POST automagically
	"propfind": do_propfind,
	"put": do_put,
	"rm": do_delete,
	"trace": do_trace,
	"update": do_update,
	"uninstall": do_uninstall,
}

if __name__ == '__main__':
	# warn users if they are using an older version of Python
	if WARNVER and not (sys.version_info[0] >= REQMAJ and sys.version_info[1] >= REQMIN):
		print >> sys.stderr, ("Warning: You may have problems with Python < %d.%d (you have %d.%d)" %
		(REQMAJ, REQMIN, sys.version_info[0], sys.version_info[1]))
	
	argv = sys.argv # eg ['get', 'http://samj.net/']
	# check if we were called directly (eg 'cush.py get http://samj.net/')
	if os.path.basename(argv[0]).lower() == SELF: argv = sys.argv[1:]	
	if len(argv) > 0: command = os.path.basename(argv[0]).lower()

	# check if we need to pass through to the OS (eg overloaded command that was meant for us)
	if (command in PASSTHRU) and ((len(argv) == 1) or (urlparse(argv[1])[0] == '')):
		env = os.environ
		env['PATH'] = PATH
		os.execvpe(command,tuple(argv), env)

	if len(argv) > 1:
		commands.get(command,do_unknown)(argv[1])
	else:
		commands.get(command,do_unknown)()
