# -*- coding: utf-8 -*-

""" TransferHandler -- class to handle uploading and downloading files

    Copyright © 2009-2010, Michael "Svedrin" Ziegler <diese-addy@funzt-halt.net>
"""


from PyQt4 import QtCore

from urllib import urlopen

import httplib
import urlparse

import bz2
import base64
import threading


class TransferHandler( QtCore.QObject ):
	""" Manages up- and downloading files. """
	
	sig_download_start	= QtCore.SIGNAL( 'downloadStart(const QString)' )
	sig_download_success	= QtCore.SIGNAL( 'downloadSuccess(const QString)' )
	sig_download_fail	= QtCore.SIGNAL( 'downloadFail(const QString, const QString)' )
	sig_download_finished	= QtCore.SIGNAL( 'downloadFinished()' )
	
	sig_upload_start	= QtCore.SIGNAL( 'uploadStart(const QString)' )
	sig_upload_success	= QtCore.SIGNAL( 'uploadSuccess(const QString)' )
	sig_upload_fail 	= QtCore.SIGNAL( 'uploadFail(const QString, const int, const QString, const QString)' )
	sig_upload_finished	= QtCore.SIGNAL( 'uploadFinished()' )
	
	
	def __init__( self, username=None, password=None ):
		QtCore.QObject.__init__( self )
		
		self.downloader = None
		self.uploader   = None
		
		self.mapsdir = None
		self.mapsurl = None
		
		self.username = username
		self.password = password
	
	
	def perform_transfers( self, map_dir, map_url, download, upload ):
		""" Start the transfers listed in download and upload in seperate threads.
		
		    download and upload need to be lists of dictionaries, containing:
		     - mapname: the base name of the map to download, e.g. de_dust
		     - mapexts: list of file extensions to be downloaded, e.g. [bsp.bz2, txt, nav]
		"""
		
		self.mapsdir = map_dir
		self.mapsurl = map_url
		
		if self.downloader is None or not self.downloader.isAlive():
			self.downloader = threading.Thread( target=self.perform_downloads, args=(download,) )
			self.downloader.start()
		
		if self.uploader is None or not self.uploader.isAlive():
			self.uploader = threading.Thread( target=self.perform_uploads, args=(upload,) )
			self.uploader.start()
	
	
	def perform_downloads( self, download ):
		""" The thread worker that handles downloading files.
		
		    For documentation on the download parameter, see perform_transfers.
		"""
		for job in download:
			mapname = job['mapname']
			mapexts = job['mapexts']
			
			self.emit( TransferHandler.sig_download_start, mapname )
			
			for cur_ext in mapexts:
				if cur_ext == "bsp" and "bsp.bz2" in mapexts:
					# We won't download a bsp now because a bz2'ed file exists
					continue
				
				mapfile = urlopen( "%s/%s.%s" % ( self.mapsurl, mapname, cur_ext ) ).fp.read()
				if cur_ext == "bsp.bz2":
					mapfile = bz2.decompress( mapfile )
					cur_ext = "bsp"
				
				mapfile_fd = open( "%s/%s.%s" % ( self.mapsdir, mapname, cur_ext ), 'wb' )
				mapfile_fd.write( mapfile )
				mapfile_fd.close()
			
			self.emit( TransferHandler.sig_download_success, mapname )
		
		self.emit( TransferHandler.sig_download_finished )
	
	
	def perform_uploads( self, upload ):
		""" The thread worker that handles uploading files.
		
		    For documentation on the upload parameter, see perform_transfers.
		"""
		for job in upload:
			mapname = job['mapname']
			mapexts = job['mapexts']
			
			self.emit( TransferHandler.sig_upload_start, mapname )
			
			bspath = "%s/%s.bsp" % ( self.mapsdir, mapname )
			bspurl = "%s/%s.bsp.bz2" % ( self.mapsurl, mapname )
			
			if not self.upload_file( mapname, bspath, bspurl ):
				continue
			
			for cur_ext in mapexts:
				if cur_ext in ( "bsp", "bsp.bz2" ):
					continue
				
				filepath = "%s/%s.%s" % ( self.mapsdir, mapname, cur_ext )
				fileurl  = "%s/%s.%s" % ( self.mapsurl, mapname, cur_ext )
				
				self.upload_file( mapname, filepath, fileurl )
			
			self.emit( TransferHandler.sig_upload_success, mapname )
		
		self.emit( TransferHandler.sig_upload_finished )
	
	
	def upload_file( self, mapname, filepath, fileurl ):
		""" See if the server will accept the file, and if so, upload it. """
		if self.upload_file_real( mapname, filepath, fileurl, True ):
			return self.upload_file_real( mapname, filepath, fileurl, False )
		return False
	
	
	def upload_file_real( self, mapname, filepath, fileurl, dry = False ):
		""" Upload a file. """
		
		diskfd = open( filepath, 'rb' )
		try:
			plaincontent = diskfd.read()
		finally:
			diskfd.close()
		
		if fileurl[-3:] == 'bz2':
			fd_content = bz2.compress( plaincontent )
		else:
			fd_content = plaincontent
		
		parsedurl = urlparse.urlparse( fileurl )
		
		# Send HTTP Put request
		httpreq = httplib.HTTPS( parsedurl.netloc )
		
		httpreq.putrequest( 'PUT', parsedurl.path )
		httpreq.putheader( 'Accept', '*/*' )
		httpreq.putheader( 'Allow', 'PUT' )
		httpreq.putheader( 'Content-Type', 'application/octet-stream' )
		httpreq.putheader( 'Content-Length', str( len( fd_content ) ) )
		
		if self.username and self.password:
			httpreq.putheader( 'Authorization', base64.b64encode( '%s:%s' % ( self.username, self.password ) ) )
		
		if dry:
			httpreq.putheader( 'x-dry-run', 'true' )
		
		httpreq.endheaders()
		
		if not dry:
			print "Sending file with %s bytes (original: %d)" % ( str( len( fd_content ) ), len( plaincontent ) )
			httpreq.send( fd_content )
		
		# check reply
		errcode, errmsg, _ = httpreq.getreply()
		hfd = httpreq.getfile()
		if hfd:
			replybody = hfd.read()
		else:
			replybody = ""
		
		if errcode != 201:
			self.emit( TransferHandler.sig_upload_fail, mapname, errcode, errmsg, replybody )
			return False
		
		httpreq.close()
		return True






