#!/usr/bin/python -O
#
# Description:
# ------------
# This is the client-side portion of the Trusted User package
# manager.  The TUs will use this program to upload packages into
# the AUR.  For more information, see the ../README.txt file.
#
# Python Indentation:
# -------------------
# For a vim: line to be effective, it must be at the end of the
# file.  See the end of the file for more information.
#

import sys
import socket
import os
import struct
import os.path
import cgi
import urllib
import getopt
import ConfigParser

from hashlib import md5

class ClientFile:
	def __init__(self, pathname):
		self.pathname = pathname
		self.filename = os.path.basename(pathname)
		self.fd = open(pathname, "rb")
		self.fd.seek(0, 2)
		self.size = self.fd.tell()
		self.fd.seek(0)
		self.makeMd5()

	def makeMd5(self):
		md5sum = md5()
		while self.fd.tell() != self.size:
			md5sum.update(self.fd.read(1024))
		self.md5 = md5sum.hexdigest()

class ClientSocket:
	def __init__(self, files, host, port, username, password):
		self.files = files
		self.host = host
		self.port = port
		self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
		self.username = username
		self.password = password

	def connect(self):
		self.socket.connect((self.host, self.port))

	def reliableRead(self, size):
		totalread = ""
		while len(totalread) < size:
			read = self.socket.recv(size-len(totalread))
			if read == 0:
				raise RuntimeError, "socket connection broken"
			totalread += read
		return totalread

	def sendMsg(self, msg):
		if type(msg) == dict:
			msg = urllib.urlencode(msg,1)
		length = struct.pack("H", socket.htons(len(msg)))
		self.socket.sendall(length)
		self.socket.sendall(msg)

	def readMsg(self, format=0):
		initsize = self.reliableRead(2)
		(length,) = struct.unpack("H", initsize)
		length = socket.ntohs(length)
		data = self.reliableRead(length)
		if format == 1:
			qs = cgi.parse_qs(data)
			return qs
		else:
			return data

	def close(self):
		self.socket.close()

	def auth(self):
		msg = {'username': self.username, 'password': self.password}
		self.sendMsg(msg)
		reply = self.readMsg(1)
		if reply['result'] == ["PASS"]:
			return 1
		elif reply['result'] == ["SQLERR"]:
			print "SQL server-side error"
			return 0
		else:
			return 0

	def sendFileMeta(self):
		msg = {'numpkgs': len(self.files)}
		for i, v in enumerate(self.files):
			msg['name'+str(i)] = v.filename
			msg['size'+str(i)] = v.size
			msg['md5sum'+str(i)] = v.md5
		self.sendMsg(msg)
		reply = self.readMsg(1)
		print reply
		for i in reply:
			if i[:4] == 'size':
				self.files[int(i[4:])].cur_done = int(reply[i][0])

	def sendFiles(self):
		for i in self.files:
			i.fd.seek(i.cur_done)
			print "Uploading:", i.filename, str(i.size/1024), "kb"
			sdone = 0
			while i.fd.tell() < i.size:
				sdone+=1
				self.socket.sendall(i.fd.read(1024))
				if sdone % 100 == 0:
					print "\r",
					print str(sdone), "of", str(i.size/1024), "kb",
					sys.stdout.flush()
		reply = self.readMsg(1)
		print reply
		self.sendMsg("ack")

def usage():
	print "usage: tupkg [options] <package file>"
	print "options:"
	print "  -u, --user     Connect with username"
	print "  -P, --password Connect with password"
	print "  -h, --host     Connect to host"
	print "  -p, --port     Connect to host on port (default 1034)"
	print "May also use conf file: ~/.tupkg"

def main(argv=None):
	if argv is None:
		argv = sys.argv

	confdict = {}
	conffile = os.path.join(os.getenv("HOME"),".tupkg") #try the standard location
	#Set variables from file now, may be overridden on command line
	if os.path.isfile(conffile):
		config = ConfigParser.ConfigParser()
		config.read(conffile)
		confdict['user'] = config.get('tupkg','username')
		confdict['password'] = config.get('tupkg','password')
		try:
			confdict['host'] = config.get('tupkg','host')
		except:
			confdict['host'] = 'aur.archlinux.org'
		try:
			confdict['port'] =  config.getint('tupkg','port')
		except:
			confdict['port'] = 1034
	else:
		confdict['user'] = ""
		confdict['password'] = ""
		confdict['host'] = 'aur.archlinux.org'
		confdict['port'] = 1034
		if len(argv) == 1: #no config file and no args, bail
			usage()
			return 1

	try:
		optlist, args = getopt.getopt(argv[1:], "u:P:h:p:", ["user=", "password=", "host=", "port="])
	except getopt.GetoptError:
		usage()
		return 1

	for i, k in optlist:
		if i in ('-u', '--user'):
			confdict['user'] = k
		if i in ('-P', '--password'):
			confdict['password'] = k
		if i in ('-h', '--host'):
			confdict['host'] = k
		if i in ('-p', '--port'):
			confdict['port'] = int(k)

	files = []
	for i in args:
		try:
			files.append(ClientFile(i))
		except IOError, err:
			print "Error: " + err.strerror + ": '" + err.filename + "'"
			usage()
			return 1

	cs = ClientSocket(files, confdict['host'], confdict['port'], confdict['user'], confdict['password'])
	try:
		cs.connect()

		if not cs.auth():
			print "Error authenticating you, you bastard"
			return 1

		cs.sendFileMeta()

		cs.sendFiles()

		cs.close()
	except KeyboardInterrupt:
		print "Cancelling"
		cs.close()

	return 0

if __name__ == "__main__":
	sys.exit(main())

# Python Indentation:
# -------------------
# Use tabs not spaces.  If you use vim, the following comment will
# configure it to use tabs.
#
# vim:noet:ts=2 sw=2 ft=python
