#!/usr/bin/python -tt

import sys
import urlparse
import urllib
import webbrowser
import string
import imaplib
import os
import re

from email import encoders
from email.message import Message
from email.mime.audio import MIMEAudio
from email.mime.base import MIMEBase
from email.mime.image import MIMEImage
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
import mimetypes

import imaplib

import gconf

import gtk
import gtk.glade

import gnomekeyring as gkey

import dbus

class GGError( Exception ):
	def __init__(self, value ):
		self.value = value

	def __str__( self ):
		return repr( self.value )


class GMailIMAP( ):
	""" Handle mailto URLs that include 'attach' fields by uploading the messages using IMAP """

	def __init__( self, mailDict  ):
		self.mailDict = mailDict

		self.messageText = self.formMessage()

	def formMessage( self ):


		msg = MIMEMultipart()

		for header in ( "To", "Cc", "Bcc", "Subject" ):
			if header.lower() in self.mailDict:
				#print "Adding ", header, self.mailDict[ header.lower() ][0]
				msg[ header ] = self.mailDict[ header.lower() ][0]

		if "subject" not in self.mailDict:
			msg[ "Subject" ] = "Sending %s" % self.mailDict[ "attach" ][0]

		msg.preamble = "Mime message attached"


		" loop is superfluous - mailDict will have only one path"
		for filename in self.mailDict[ 'attach' ]:

			filepath = urlparse.urlsplit( filename ).path

			if not os.path.isfile( filepath ):
				raise GGError( "File not found - %s" % filepath )

			ctype, encoding = mimetypes.guess_type( filepath )

			if ctype is None or encoding is not None:
				ctype = 'application/octet-stream'

			maintype, subtype = ctype.split( '/', 1 )

			fp = open( filepath, 'rb' )

			if maintype == 'text':
				attachment = MIMEText( fp.read(), _subtype = subtype )
			elif maintype == 'image':
				attachment = MIMEImage( fp.read(), _subtype = subtype )
			elif maintype == 'audio':
				attachment = MIMEAudio( fp.read(), _subtype = subtype )
			else:
				attachment = MIMEBase( maintype, subtype )
				attachment.set_payload( fp.read() )
				encoders.encode_base64( attachment )

			fp.close()

			attachment.add_header( 'Content-Disposition', 'attachment', filename=filepath)

			msg.attach( attachment )


		return( msg.as_string() )


	def sendMail( self, user, password ):
		imapObj = imaplib.IMAP4_SSL( "imap.gmail.com" )

		imapObj.login( user, password )

		try:
			draftFolder = imapObj.lsub( "/", "*]/Drafts" )[1][0]

			draftFolder = re.search( '[^\"]+\]/Drafts', draftFolder ).group(0)

		except:
			raise GGError( "Unable to determine IMAP Drafts folder location" )

		result = imapObj.append( draftFolder, None, None, self.messageText )

		imapObj.logout()



class Keyring(object):
	def __init__(self, name, server, protocol, port):
		self._name = name
		self._server = server
		self._protocol = protocol
		self._port = port

	def has_credentials(self):
		try:
			attrs = {"server": self._server, 
				"protocol": self._protocol}
			items = gkey.find_items_sync(gkey.ITEM_NETWORK_PASSWORD, attrs)
			return len(items) > 0
		except gkey.DeniedError:
			return False

	def get_credentials(self, user):
		attrs = {"server": self._server, 
			"protocol": self._protocol,
			"user": user }
		items = gkey.find_items_sync(gkey.ITEM_NETWORK_PASSWORD, attrs)

		return (items[0].attributes["user"], items[0].secret)

	def set_credentials(self, (user, pw)):
		attrs = {
			"user": user,
			"server": self._server,
			"protocol": self._protocol,
			}
		gkey.item_create_sync(None,
		gkey.ITEM_NETWORK_PASSWORD, self._name, attrs, pw, True)

class ConfigInfo():
	def __init__( self, basedir = '/apps/gnome-gmail/' ):
		self.client = gconf.client_get_default()
		self.basedir = basedir

		self.keyring = Keyring( "GMail", "imap.google.com", "imap", "143" )

		self.user = ""
		self.password = ""
		self.savepassword = False
		self.appsdomain = ""
		self.hideconfirmation = False

		#self.keyring.set_credentials( (self.user, "") )

	def readConfig( self ):
		self.user = self.client.get_string( self.basedir + 'user' )
		if self.user == None:
			self.user = ""

		self.savepassword = self.client.get_bool( self.basedir + 'savepassword' )
		if self.savepassword == None:
			self.savepassword = False

		self.appsdomain = self.client.get_string( self.basedir + 'appsdomain' )
		if self.appsdomain == None:
			self.appsdomain = ""

		self.hideconfirmation = self.client.get_bool( self.basedir + 'hideconfirmation' )
		if self.hideconfirmation == None:
			self.hideconfirmation = False

		try:
			if self.savepassword:
				(luser, self.password ) = self.keyring.get_credentials(self.user)

			if self.password == None:
				self.password = ""
		except:
			pass

	def writeConfig( self ):
		self.client.set_string( self.basedir + 'user', self.user )
		self.client.set_bool( self.basedir + 'savepassword', self.savepassword )
		self.client.set_string( self.basedir + 'appsdomain', self.appsdomain )
		self.client.set_bool( self.basedir + 'hideconfirmation', self.hideconfirmation )

		if self.savepassword:
			self.keyring.set_credentials( (self.user, self.password) )

	def infoComplete( self ):
		return( len( self.password) > 0 and len( self.user) > 0  )

	def gconfToDialog( self, foo = 0, bar=0 ):

		self.readConfig()

		user_field = self.xml.get_widget( 'entryUserName' )
		user_field.set_text( self.user )

		password_field = self.xml.get_widget( 'entryPW' )
		password_field.set_text( self.password )

		savepassword_field = self.xml.get_widget( 'checkbuttonPW' )
		savepassword_field.set_active( self.savepassword )
		
	def dialogToGconf( self ):
		user_field = self.xml.get_widget( 'entryUserName' )
		self.user = user_field.get_text()

		password_field = self.xml.get_widget( 'entryPW' )
		self.password = password_field.get_text()

		savepassword_field = self.xml.get_widget( 'checkbuttonPW' )
		self.savepassword = savepassword_field.get_active()

		self.writeConfig()
		
	def callConfigure( self, foo, bar = 1 ):

		self.dialogToGconf()

		os.system( "/usr/bin/gconf-editor /apps/gnome-gmail/appsdomain &" )

		return( True )


	def queryConfigInfo( self ):

		self.xml = gtk.glade.XML( "/usr/share/gnome-gmail/gnomegmail.glade", domain="gnome-gmail" )

		configButton = self.xml.get_widget( "buttonConfigure") 
		hid = configButton.connect( "button-press-event", self.callConfigure )

		wind = self.xml.get_widget( "dialog1" )
		# todo - should map window focus event to gconfToDialog(), but it isn't working

		self.gconfToDialog()

		self.window = self.xml.get_widget( "dialog1" )
		response = self.window.run()

		if response == 1:
			self.dialogToGconf()
		else:
			raise GGError( "User cancelled \"Send To...\"" )

		self.window.destroy()

		
		return( self.infoComplete() )

class GMailURL( ):
	""" Logic to convert a mailto link to an appropriate GMail URL, by
	any means necessary, including IMAP uploads, if necessary """

	def __init__( self, mailtoURL, cfg, enableNetAccess = True ):
		self.mailtoURL = mailtoURL
		self.enableNetAccess = enableNetAccess
		self.configInfo = cfg

		self.configInfo.readConfig()

		self.appsdomainstr = self.configInfo.appsdomain
		if( len( self.appsdomainstr ) > 0 ):
			self.appsdomainstr = "a/" + self.appsdomainstr + "/"

		self.mailDict = self.mailto2dict( )


	def appendUrl( self, tourl, urltag, dict, dicttag ):
		""" Given a GMail URL underconstruction and the URL tag for the
		current	mailto dicttag, add the parameter to the URL """

		if dicttag in dict:
			tourl = tourl + "&" + urltag + "=" + urllib.quote_plus( dict[dicttag][0] )

		return( tourl )


	def mailto2dict( self ):
		""" Convert a mailto: reference to a dictionary containing the 
		message parts """
		# get the path string from the 'possible' mailto url
		usplit = urlparse.urlsplit( self.mailtoURL, "mailto" )

		path = usplit.path

		try:
			# for some reason, urlsplit is not splitting off the 
			# query string.
			# do it here
			( address, qs ) = string.split( path, "?", 1 )
		except ValueError:
			address = path

			qs = usplit.query

		qsdict = urlparse.parse_qs( qs )

		qsdict['to'] = [ address ]

		outdict = {}
		for (key, value) in qsdict.iteritems():
			for i in range(0, len(value)):
				value[i] = urllib.unquote_plus( value[i]  )

			outdict[ key.lower() ] = value

		return( outdict )

	def standardGMailURL( self ):
		""" If there is no attachment reference, create a direct GMail 
		URL which will create the message """

		dct = self.mailDict

		tourl = "https://mail.google.com/%smail?view=cm&tf=0&fs=1" % self.appsdomainstr

		tourl = self.appendUrl( tourl, "to", dct, "to" )
		tourl = self.appendUrl( tourl, "su", dct, "subject" )
		tourl = self.appendUrl( tourl, "body", dct, "body" )
		tourl = self.appendUrl( tourl, "cc", dct, "cc" )
		tourl = self.appendUrl( tourl, "bcc", dct, "bcc" )

		return( tourl )

	def simpleGMailURL( self ):
		return( "https://mail.google.com/%s" % self.appsdomainstr )


	def hasAttachment( self ):
		return( 'attach' in self.mailDict )

	def imapGMailURL( self ):
		""" if the mailto refers to an attachment, use IMAP to upload the file """

		imap_url = "https://mail.google.com/%smail/#drafts" % self.appsdomainstr

		if not self.enableNetAccess:
			return( imap_url )

		try:
			gmImap = GMailIMAP( self.mailDict )
		except:
			GGError( "Error creating message with attachment" )

		# get the configuration information from GConf
		self.configInfo.readConfig()

		# if we have a full set of configuration information, go ahead and try the transfer
		if( self.configInfo.infoComplete() ):
			try:
				gmImap.sendMail( self.configInfo.user, self.configInfo.password )
			except imaplib.IMAP4.error as inst:
				# don't report this failure. We'll try again with prompted parameters
				pass
			else:
				return( imap_url )

		# query for needed information
		if( self.configInfo.queryConfigInfo() ):
			# do the IMAP upload
			try:
				gmImap.sendMail( self.configInfo.user, self.configInfo.password )
			except imaplib.IMAP4.error as inst:
				errorStr = None
				if string.find( inst.args[0], "nvalid credentials" ) > 0:
					errorStr = "Invalid GMail User Name or Password"
				elif string.find( inst.args[0], "not enabled for IMAP" ) > 0:
					errorStr = "You must 'Enable IMAP' in Gmail in order to send attachments"
				else:
					errorStr = inst.args[0]

				raise GGError( errorStr )

			#self.configInfo.writeConfig()
		else:
			raise GGError( "GMail credentials incomplete" )

		return( imap_url )

	def gmailURL( self ):
		""" Return a GMail URL appropriate for the mailto handled by this instance """
		if( len( self.mailtoURL ) == 0 ):
			gmailurl = self.simpleGMailURL() 
		elif self.hasAttachment():
			gmailurl = self.imapGMailURL()
		else:
			gmailurl = self.standardGMailURL( )

		return( gmailurl )

loop = None
notificationId = None

def notificationSignal( id, actionKey ):
	global notificationId

	if id == notificationId:

		if actionKey == "configuregg":
			os.system( "/usr/bin/gconf-editor /apps/gnome-gmail/appsdomain &" )

		if actionKey == "hidegg":
			cfg = ConfigInfo()
			cfg.readConfig()
			cfg.hideconfirmation = True
			cfg.writeConfig()

def notificationClosed( id, bar ):
	global loop
	global notificationId

	if id == notificationId:
		if loop:
			loop.quit()

def main( ):
	global loop
	global notificationId

	if( len( sys.argv ) > 1 ):
		mailto = sys.argv[1]
	else:
		mailto = ""

	cfg = ConfigInfo()
	cfg.readConfig()
	cfg.writeConfig()

	import dbus.glib
	loop = dbus.mainloop.glib.DBusGMainLoop()
	sessionbus = dbus.SessionBus( mainloop = loop )
	notifications_object = sessionbus.get_object(
		'org.freedesktop.Notifications',
		'/org/freedesktop/Notifications')
	interface = dbus.Interface(
		notifications_object,
		'org.freedesktop.Notifications')

	sessionbus.add_signal_receiver( notificationSignal, "ActionInvoked" )
	sessionbus.add_signal_receiver( notificationClosed, "NotificationClosed" )

	try:
		gm = GMailURL( mailto, cfg )
		gmailurl = gm.gmailURL()
	except GGError as gerr:
		interface.Notify( "gnome-gmail", 0, '', "gnome-gmail", gerr.value, [], {}, 3000 )
	else:
		webbrowser.open_new_tab( gmailurl )

	cfg.hideconfirmation = 1
	if not cfg.hideconfirmation:
		notificationId = interface.Notify( "gnome-gmail", 0, '', 
			"Gnome Gmail",
			 "Email message created by <a href=\"http://gnome-gmail.sourceforge.net\">Gnome Gmail</a>", 
			[ "configuregg", "Configure Google Apps support",
			  "hidegg", "Don't show this again" ],
			{ "urgency":0, "category":"email"}, 7000 )

		import gobject
		loop = gobject.MainLoop()
		loop.run()

if __name__ == "__main__":
	main()



