
from struct import pack, unpack

from string import split, join, find
string_join = join
string_split = split

from os import environ, sep, close, open, fork, remove, symlink, _exit, \
	O_WRONLY, listdir, execvp, O_CREAT, O_APPEND, write, error, chdir, \
	rename
os_open = open ; del open
os_error = error ; del error

from os.path import isfile, expanduser, exists, isdir, join, islink, \
	split, abspath
path_join = join ; join = string_join ; del string_join
path_split = split ; split = string_split ; del string_split

from sys import stdout, stderr, exc_info

import sys

from traceback import format_exception

from socket import socket, AF_INET, SOCK_STREAM, error
socket_error = error ; del error

from select import select

from gennylib import broadcast, for_me, respond, rawip2ip, hostorip2rawip

# The file service.
GENNY_FILE = 6346		# Finding files

SEARCH = 0			# I want this file
SEARCHNC = 1			# Exclude cached hits
HITS = 128			# These files are here
HITSNC = 129			# These hits are not cached
PUSHREQ = 64			# Please send me this file
LISTREQ = 32			# I want a list of files
LISTAT = 33			# A list of files is here
GETREQ = 96
GETAT = 97
ERROR = 255			# This error happened
NUMERICS = SEARCH, SEARCHNC, HITS, HITSNC, PUSHREQ, LISTREQ, LISTAT, GETREQ, \
	GETAT, ERROR

ERROR_CORRUPT = 0
ERROR_BADNUMERIC = 1		# No such GENNY_FILE numeric.
ERROR_NOTFOUND = 2		# No such pathname
ERROR_BADPATHNAME = 3		# Invalid pathname
ERROR_BADPORT = 4		# Invalid destination port
ERROR_BADADDR = 5		# Invalid destination address
ERROR_NOBANDWIDTH = 6		# I can't upload
ERROR_DIDNTUPLOAD = 7		# I did not try to upload
ERROR_UPLOADFAIL = 8		# Upload attempt failed

def handle_command_find(config, connections, identifiers, arguments):
	if not arguments:
		print "usage:  find search terms"
	else:
		print "Searching for: %s" % `arguments`
		payload = pack('B', SEARCH) + arguments
		broadcast(config, connections, identifiers, GENNY_FILE, payload)
	return

def handle_command_response(config, arguments):
	print "Search responses:"
	for result in config['results']:
		ipaddy, port, pathname = result[1:4]
		i = config['results'].index(result)
		if arguments:
			if search(pathname, arguments):
				print "%s http://%s:%s/%s" % (i, ipaddy, port, \
					pathname)
		else:
			print "%s http://%s:%s/%s" % (i, ipaddy, port, pathname)
	return

def handle_command_get(config, arguments):
	if not arguments or ' ' in arguments:
		print "The get command takes exactly one argument."
		return
	try:
		num = int(arguments)
	except ValueError:
		print "That is not int()able..."
		return
	if num >= len(config['results']):
		print "No such search result..."
		return
	# Nonportable, twenty-four lines
	getting_tool = None
	for path in split(environ['PATH'], ':'):
		if isfile(path_join(path, 'wget')):
			getting_tool = 'wget'
			break
		if isfile(path_join(path, 'fetch')):
			getting_tool = 'fetch'
			break
	if not getting_tool:
		print "I can't find wget or fetch, get failed."
		return
	the_url = 'http://%s:%s/%s' % config['results'][num][1:4]
	pid = fork()
	if pid == 0:
		try:
			for file in config['closeme']:
				file.close()
			logfile = expanduser("~/.genny/misc.log")
			close(stdout.fileno())
			os_open(logfile, O_WRONLY|O_CREAT|O_APPEND)
			close(stderr.fileno())
			os_open(logfile, O_WRONLY|O_CREAT|O_APPEND)
			chdir(config['downloadpath'])
			if getting_tool == 'wget':
				execvp('wget', ['wget', '-NcT180', the_url])
			else:
				execvp('fetch', ['fetch', the_url])
		except:
			write(2, "%s\n" % `exc_info()`)
                        blurb = format_exception(sys.exc_type, sys.exc_value, \
                                sys.exc_traceback)
                        for line in blurb:
                                write(2, line)
		_exit(1)
	else:
		config['transfers'][pid] = "HTTP download of %s" % \
			config['results'][num][3]
	return

def handle_command_lclear(config, arguments):
	config['results'] = []
	return

def visit(pathnames, share_dir, dirname, names):
	dirname = dirname[len(share_dir)+len(sep):]
	for filename in names:
		pathname = path_join(dirname, filename)
		if isdir(pathname):
			pass
		elif dirname:
			pathnames.append(pathname)
		else:
			pathnames.append(filename)

def walk(top, pathnames, share_dir):
	try:
		names = listdir(top)
	except os_error:
 		return
	visit(pathnames, share_dir, top, names)
	exceptions = ('.', '..')
	for name in names:
		if name not in exceptions:
			name = path_join(top, name)
			if isdir(name):
				walk(name, pathnames, share_dir)

def handle_command_share(config, arguments):
	if not arguments:
		print `config['share']`
	# FIXME:  I need support for more than one path per baseurl.
	elif ' ' in arguments:
		print "The share command takes one or zero arguments."
	else:
		share_dir = expanduser(arguments)
		if not isdir(share_dir):
			print "%s is not a directory." % share_dir
			return
		sharelink = expanduser("~/.genny/share")
		if exists(sharelink):
			remove(sharelink)
		# Nonportable, one line
		symlink(abspath(share_dir), sharelink)
		pathnames = []
		walk(share_dir, pathnames, share_dir)
		config['share'] = tuple(pathnames)
	return

def handle_command_webserve(config, arguments):
	if arguments:
		try:
			port = int(arguments)
		except:
			print "that argument is not int()able"
			return
	else:
		port = config['webport']
	pid = fork()
	if pid == 0:
		try:
			for file in config['closeme']:
				file.close()
			logfile = expanduser("~/.genny/misc.log")
			close(stdout.fileno())
			os_open(logfile, O_WRONLY|O_CREAT|O_APPEND)
			close(stderr.fileno())
			os_open(logfile, O_WRONLY|O_CREAT|O_APPEND)
			execvp("python", ("python", "webserve.py", str(port)))
		except:
                        write(2, "%s\n" % `exc_info()`)
                        blurb = format_exception(sys.exc_type, sys.exc_value, \
                                sys.exc_traceback)
                        for line in blurb:
                                write(2, line)
		_exit(1)
	else:
		config['webserverpid'] = pid
	return

def handle_command_push(config, identifiers, arguments):
        if not arguments or ' ' in arguments:
                print "The push command takes exactly one argument."
                return
        try:
                num = int(arguments)
        except ValueError:
                print "That is not int()able..."
                return
        if num >= len(config['results']):
                print "No such search result..."
                return
	ipaddy = config['myip']
	listen_sock = socket(AF_INET, SOCK_STREAM)
	listen_sock.listen(1)
	port = listen_sock.getsockname()[1]
	port_timeout = 180
	first_byte = 0
	last_byte = 0
	pathname = config['results'][num][3]
	payload = pack('B', PUSHREQ) + ipaddy + pack('HHHH', port, \
		port_timeout, first_byte, last_byte) + pathname
	routing_header = config['results'][num][0]
	respond(config, identifiers, routing_header, GENNY_FILE, payload)
	pid = fork()
	if pid == 0:
		try:
			for file in config['closeme']:
				file.close()
			logfile = expanduser("~/.genny/misc.log")
			close(stdout.fileno())
			os_open(logfile, O_WRONLY|O_CREAT|O_APPEND)
			close(stderr.fileno())
			os_open(logfile, O_WRONLY|O_CREAT|O_APPEND)
			chdir(config['downloadpath'])
			if not select([listen_sock], [], [], port_timeout)[0]:
				write(2, "select timed out\n")
				_exit(1)
			getting_sock, addr = listen_sock.accept()
			listen_sock.close()
			filename = path_split(pathname)[1]
			writing_file = open("%s.tmp" % filename, 'a')
			data = getting_sock.recv(65536)
			while data:
				writing_file.write(data)
				data = getting_sock.recv(65536)
			getting_sock.close()
			writing_file.close()
			if not exists(filename):
				rename("%s.tmp" % filename, filename)
			_exit(0)
                except:
			write(2, "%s\n" % `exc_info()`)
			blurb = format_exception(sys.exc_type, sys.exc_value, \
				sys.exc_traceback)
			for line in blurb:
				write(2, line)
                _exit(1)
	else:
		listen_sock.close()
		config['transfers'][pid] = "Push download of %s" % pathname
	return

def handle_command_webip(config, arguments):
	if not arguments:
		print "webip is:  %s" % rawip2ip(config['webip'])
	elif ' ' in arguments:
		print "usage:  webip [ip.ad.dr.es]"
        else:
                try:
			config['webip'] = hostorip2rawip(arguments)
			if config['loglevel'] > 0:
				print "webip set to: %s" % \
					rawip2ip(config['webip'])
                except:
                        print "I can't convert that."
	return

def handle_command_webport(config, arguments):
	if not arguments:
		print "webport is: %s" % config['webport']
	elif ' ' in arguments:
		print "usage:  webport [port]"
	else:
		try:
			config['webport'] = int(arguments)
			if config['loglevel'] > 0:
				print "webport set to: %s" % config['webport']
                except ValueError:
                        print "That doesn't seem to be a number."
	return

def handle_command_downloadpath(config, arguments):
	if not arguments:
		print "downloadpath is %s" % `config['downloadpath']`
	else:
		config['downloadpath'] = expanduser(arguments)
	return

def handle_genny_file(config, identifiers, r_packet):
        if len(r_packet) < 13:
                if config['loglevel'] > 1:
                        print "File packet too small %s: " % conn + \
                                `r_packet`
                payload = pack('BB', ERROR, ERROR_CORRUPT) + \
			"Packet was too small."
                respond(config, identifiers, r_packet[:12], GENNY_FILE, payload)
		return
        numeric = unpack('B', r_packet[12])[0]
	if numeric in (SEARCH, SEARCHNC):
		if config['share']:
			handle_search(config, identifiers, r_packet)
	elif numeric in (HITS, HITSNC):
		if for_me(identifiers, r_packet):
			handle_hits(config, identifiers, r_packet)
	elif numeric == PUSHREQ:
		if for_me(identifiers, r_packet):
			handle_pushreq(config, identifiers, r_packet)
	elif numeric == LISTREQ:
		pass
		#if for_me(identifiers, r_packet):
		#	handle_listreq(config, identifiers, r_packet)
	elif numeric == LISTAT:
		pass
		#if for_me(identifiers, r_packet):
		#	handle_listat(config, identifiers, r_packet)
	elif numeric == ERROR:
		if for_me(identifiers, r_packet):
			if config['loglevel'] > 0:
				print "File error message %s received: %s" % \
					(unpack('B', r_packet[13])[0], \
					`r_packet[14:]`)
		#	handle_error(config, identifiers, r_packet)
	elif numeric in NUMERICS:
		if for_me(identifiers, r_packet):
			if config['loglevel'] > 0:
				print "Ignoring legit File numeric %s: %s" % \
					(numeric, `r_packet`)
	else:
		if config['loglevel'] > 2:
			print "numeric %s not supported" % `r_packet[12]`
		if for_me(identifiers, r_packet):
			payload = pack('BB', ERROR, ERROR_BADNUMERIC) + \
				"numeric not supported: %s" % `r_packet[12]`
                	respond(config, identifiers, r_packet, GENNY_FILE, \
	                        payload)
	return

def handle_search(config, identifiers, r_packet):
	# FIXME:  Single HITS packets can get too large.
	# FIXME:  This is only set up to handle one sharing archive on one
	# IP...no caching
	hitslist = []
	hitslen = 0
	for pathname in config['share']:
		if search(pathname, r_packet[13:]):
			# The limit is 64kiB minus hits header minus genny
			# header minus udp header minus IP header...I think
			if hitslen + len(pathname) > 65487:
				break
			else:
				hitslist.append(pathname)
				hitslen = hitslen + len(pathname) + 1
	if hitslist:
		rawipaddy = config['webip']
		rawport = pack('H', config['webport'])
		payload = pack('BB', HITSNC, 1) + rawipaddy + rawport + \
			join(hitslist, '\0')
		respond(config, identifiers, r_packet[:12], GENNY_FILE, payload)
	return

def handle_hits(config, identifiers, r_packet):
	ipaddy = rawip2ip(r_packet[14:18])
	port = unpack('H', r_packet[18:20])[0]
	for pathname in split(r_packet[20:], '\0'):
		hit = (r_packet[:12], ipaddy, port, pathname)
		if hit not in config['results']:
			config['results'].append(hit)
			if config['loglevel'] > 0:
				i = config['results'].index(hit)
				print "%s http://%s:%s/%s" % (i, ipaddy, port, \
					pathname)
	return

def handle_pushreq(config, identifiers, r_packet):
	ipaddy = rawip2ip(r_packet[13:17])
	# FIXME:  The port timeout is not used either side yet.
	# FIXME:  The file is read until EOF anyways
	port, port_timeout, first_byte, last_byte = unpack('H', r_packet[17:25])
        if port < 1024:
                payload = pack('BB', ERROR, ERROR_UPLOADFAIL) + \
                        "won't send to privileged port: %s" % port
                respond(config, identifiers, r_packet, GENNY_FILE, payload)
                return
	pathname = r_packet[25:]
	if '\0' in pathname:
		payload = pack('BB', ERROR, ERROR_BADPATHNAME) + \
			"pathname cannot have a NUL 0: %s" % `pathname`
		respond(config, identifiers, r_packet, GENNY_FILE, payload)
		return
	new_pathname = path_join(expanduser("~/.genny/share"), pathname)
	if config['loglevel'] > 1:
		print "ipaddy -> %s, port -> %s, port_timeout -> %s, " \
"first_byte -> %s, last_byte -> %s, pathname -> %s" % (ipaddy, port, \
port_timeout, first_byte, last_byte, `pathname`)
	if not isfile(new_pathname):
		payload = pack('BB', ERROR, ERROR_NOTFOUND) + \
			"pathname not found: %s" % `pathname`
		respond(config, identifiers, r_packet, GENNY_FILE, payload)
		return
	pid = fork()
	if pid == 0:
		try:
			for file in config['closeme']:
				file.close()
			logfile = expanduser("~/.genny/misc.log")
			close(stdout.fileno())
			os_open(logfile, O_WRONLY|O_CREAT|O_APPEND)
			close(stderr.fileno())
			os_open(logfile, O_WRONLY|O_CREAT|O_APPEND)
			f = open(new_pathname)
			f.seek(first_byte)
			s = socket(AF_INET, SOCK_STREAM)
			s.connect((ipaddy, port))
			data = f.read(65536)
			while data:
				s.send(data)
				data = f.read(65536)
			s.close()
			f.close()
			_exit(0)
                except:
                        write(2, "%s\n" % `exc_info()`)
                        blurb = format_exception(sys.exc_type, sys.exc_value, \
                                sys.exc_traceback)
                        for line in blurb:
                                write(2, line)
                _exit(1)
	else:
		config['transfers'][pid] = "Push upload of %s" % pathname
	return
	
def search(name, terms):
	for i in split(terms):
		if find(name, i) == -1:
			return 0
	return 1
