#!/usr/bin/python3
#
# Copyright (c) 2003,2004,2005,2006,2007,2011,2017,2020 Olivier Sessink
# All rights reserved.
#
#Redistribution and use in source and binary forms, with or without
#modification, are permitted provided that the following conditions 
#are met:
#  * Redistributions of source code must retain the above copyright 
#    notice, this list of conditions and the following disclaimer.
#  * Redistributions in binary form must reproduce the above 
#    copyright notice, this list of conditions and the following 
#    disclaimer in the documentation and/or other materials provided 
#    with the distribution.
#  * The names of its contributors may not be used to endorse or 
#    promote products derived from this software without specific 
#    prior written permission.
#
#THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
#"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
#LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 
#FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 
#COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 
#INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 
#BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 
#LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
#CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 
#LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 
#ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
#POSSIBILITY OF SUCH DAMAGE.
#

VERSIONSTRING = '3.0'
DATADIR = '/usr/share/directoryassistant/'


#import gtk
#import gobject
import ldap
import string
import configparser
import os
import gettext
#import vobject
import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk, GObject

class field:
	def __init__(self,name,title='',editable=0,multiline=0,inOrg=0):
		self.name = name
		self.title = title
		self.editable = editable
		self.multiline = multiline
		self.inOrg = inOrg
		self.action = None
		self.hide = 0

LDAP_KEY_MAP = []

def init_key_map(cfg):
	LDAP_KEY_MAP[:] = []
	LDAP_KEY_MAP.append(field('cn',_('Common name'),0,0))
	LDAP_KEY_MAP.append(field('sn',_('Surname'),1,0))
	LDAP_KEY_MAP.append(field('givenName',_('Given name'),1,0))
#	LDAP_KEY_MAP.append(field('personalTitle',_('Title'),1,0))
	LDAP_KEY_MAP.append(field('o',_('Organisation'),1,0,1))
	LDAP_KEY_MAP.append(field('mail',_('Email'),1,0))
	LDAP_KEY_MAP.append(field('telephoneNumber',_('Business phone number'),1,0,1))
	LDAP_KEY_MAP.append(field('facsimileTelephoneNumber',_('Fax number'),1,0,1))
	LDAP_KEY_MAP.append(field('mobile',_('Mobile phone number'),1,0))
	LDAP_KEY_MAP.append(field('street',_('Street'),1,0,1))
	LDAP_KEY_MAP.append(field('postOfficeBox',_('P.O. Box'),1,0,1))
	LDAP_KEY_MAP.append(field('postalAddress',_('Postal address'),1,1,1))
	LDAP_KEY_MAP.append(field('postalCode',_('Postal code'),1,0,1))
	LDAP_KEY_MAP.append(field('l',_('Locality'),1,0,1))
	LDAP_KEY_MAP.append(field('homePhone',_('Home phone number'),1,0))
	LDAP_KEY_MAP.append(field('homePostalAddress',_('Home address'),1,1))
	LDAP_KEY_MAP.append(field('description',_('Description'),1,1,1))
	
	defs = cfg.defaults()
	if (len(defs)):
		for k in defs:
			if (k[0:10]=='keyaction_'):
				i= index_for_key(k[10:])
				if (i != None):
					LDAP_KEY_MAP[i].action = defs[k]
			if (k[0:8]=='keyhide_'):
				i= index_for_key(k[8:])
				if (i != None):
					LDAP_KEY_MAP[i].hide = 1

def all_keys():
	ret = []
	for field in LDAP_KEY_MAP:
		ret = ret + [field.name]
	return ret

def all_editable_keys(inOrg=0):
	ret = []
	for field in LDAP_KEY_MAP:
		if (field.editable and (inOrg == 0 or field.inOrg == 1)):
			ret = ret + [field.name]
	return ret

def index_for_key(key):
	i=len(LDAP_KEY_MAP)
	while (i>0):
		i -= 1
#		print('index',i)
		if (LDAP_KEY_MAP[i].name.lower() == key.lower()):
			return int(i)
	print('did not find index for key',key)
	return None

def field_for_key(key):
	for field in LDAP_KEY_MAP:
		if (field.name.lower() == key.lower()):
			return field
	return None

def dn_escape(val):
	s = val.replace('\\', '\\\\')
	for char in ',','<','>','=','+',';','"': 
		s = s.replace(char,'\\'+char)
	if (s[0] == '#' or s[0] == ' '):
		s = '\\'+s
	if (s[-1] == ' '):
		s = '\\'+s
	return s

#def dn_unescape(val):
#	s = val.replace('\\\\', '\\')
#	s = s.replace('', '')

class MyEntry:
	widget = None
	multiline = 0
	
	def __init__(self,multiline=0):
		self.multiline = multiline
		if (self.multiline):
			self.buffer = Gtk.TextBuffer()
			self.view = Gtk.TextView(buffer=self.buffer)
			self.widget = Gtk.ScrolledWindow()
			self.widget.add(self.view)
			self.widget.set_policy(Gtk.PolicyType.AUTOMATIC,Gtk.PolicyType.AUTOMATIC)
			self.widget.set_shadow_type(Gtk.ShadowType.ETCHED_IN)
		else:
			self.widget = Gtk.Entry()

	def get_text(self):
		if (self.multiline):
			iters = self.buffer.get_bounds()
			tmp = self.buffer.get_text(iters[0],iters[1],True)
		else:
			tmp = self.widget.get_text()
		return tmp
	
	def set_text(self,text):
		if (isinstance(text, bytes)):
			text = text.decode('utf-8')
		if (self.multiline):
			self.buffer.set_text(text)
		else:
			self.widget.set_text(text)

class LdapBackend:
	"This class will do all actual ldap communication"

	ld = None
	
	def connect(self,debug=0):
#		print('connect to ',self.ldapurl,'with version',self.ldapversion)
		if (self.ldapurl[0:7] != 'ldap://' and self.ldapurl[0:8] != 'ldapi://' and self.ldapurl[0:8] != 'ldaps://'):
			self.ldapurl = 'ldap://'+self.ldapurl+'/' 
		self.ld = ldap.initialize(self.ldapurl,trace_level=debug)
		if (self.ldapversion != None):
			self.ld.set_option(ldap.OPT_PROTOCOL_VERSION,self.ldapversion)
		if (self.binddn != None and self.bindpw != None):
#			print('try user ',self.binddn)
			try:
				self.ld.simple_bind_s(self.binddn, self.bindpw)
			except ldap.INVALID_CREDENTIALS:
				print('Invalid ldap user or password')
	
	def __init__(self,cfg,section,debug=0):
		self.name = section
		self.ldapurl = cfg.get(section, 'ldapurl')
		self.baseDN = cfg.get(section, 'base_dn')
		try:
			self.binddn = cfg.get(section, 'bind_dn')
			self.bindpw = cfg.get(section, 'bind_password')
		except configparser.NoOptionError:
			self.binddn = None
			self.bindpw = None
		try:
			self.ldapversion = int(cfg.get(section, 'ldapversion'))
		except (configparser.NoOptionError,ValueError):
			self.ldapversion = None
		try:
			self.addDN = cfg.get(section,'add_dn')
		except (configparser.NoOptionError):
			self.addDN = self.baseDN
		#self.connect(debug)
	
	def get_address(self,dn):
		print(('get_address(%s)'%(dn)))
		try:
			entry = self.ld.search_s(dn,ldap.SCOPE_BASE,attrlist=all_keys())
			print(entry)
		except ldap.SERVER_DOWN:
			self.connect(0)
			entry = self.ld.search_s(dn,ldap.SCOPE_BASE,attrlist=all_keys())
		return entry[0][1]

	def get_dnlist_name(self,name):
		if (len(name)>0):
			pat = '(|(cn=*'+name+'*)(sn=*'+name+'*)(givenName=*'+name+'*)(o=*'+name+'*))'
		else:
			pat = '(|(cn=*)(o=*))'
#		print('searching for '+pat+' in '+self.baseDN)
		try:
			try:
				res = self.ld.search_s(self.baseDN,ldap.SCOPE_SUBTREE,pat,['cn','o'])
			except (ldap.SERVER_DOWN, AttributeError): # Attributeerror if self.ld == None
				self.connect(0)
				res = self.ld.search_s(self.baseDN,ldap.SCOPE_SUBTREE,pat,['cn','o'])
		except (ldap.NO_SUCH_OBJECT):
			return {}
		results = {}
		i=len(res)-1
#		print('found ',len(res),'results')
		while (i >= 0):
			dn = res[i][0]
			if ('cn' in res[i][1]):
				name = res[i][1]['cn'][0]
			elif ('o' in res[i][1]):
				name = res[i][1]['o'][0]
			else:
				name = _('No name')
				print('no name',res[i])
			results[dn] = name
			i -= 1
		return results
	
	def list_encode(self, mylist):
		for i in range(len(mylist)):
			mylist[i] = mylist[i].encode()
		return mylist
	
	def saveAddress(self,newaddress):
		modlist = []
		# check for person or organisation
		if ('sn' in newaddress or 'givenName' in newaddress):
			modlist.append(('objectClass', b'inetOrgPerson'))
			if ('sn' in newaddress and 'givenName' in newaddress):
				cn = newaddress['givenName'][0]+' '+newaddress['sn'][0]
				print('new cn=',cn)
			elif ('sn' in newaddress):
				cn = newaddress['sn'][0]
				print('new cn=',cn)
			else:
				cn = newaddress['givenName'][0]
				print('new cn=',cn)
			modlist.append(('cn', cn.encode()))
			dn = 'cn='+dn_escape(cn)+','+self.addDN
			for key in all_editable_keys(0):
				if (key in newaddress and len(newaddress[key]) > 0):
					modlist.append((key, self.list_encode(newaddress[key])))
		elif ('o' in newaddress):
			modlist.append(('objectClass', b'organization'))
			o = newaddress['o'][0]
			dn = 'o='+dn_escape(o)+','+self.addDN
			for key in all_editable_keys(1):
				if (key in newaddress and len(newaddress[key]) > 0):
					modlist.append((key, self.list_encode(newaddress[key])))
		else:
			return 0
		print('save with dn',dn)
		try:
			self.ld.add_s(dn, modlist)
			return 1
		except ldap.SERVER_DOWN:
			self.connect(0)
			self.ld.add_s(dn, modlist)
			return 1
	
	def modifyAddress(self,dn,oldaddress,newaddress):
		modlist = []
# BUG: WHAT HAPPENS IF THERE IS A BACKSLASH ESCAPED COMMA IN THE DN !!!!!!!!!!
		pos = dn.find(',')
		if (pos > 0):
			tmpaddDN = dn[pos+1:]
		else:
			tmpaddDN = self.addDN
		if ('sn' in newaddress or 'givenName' in newaddress):
			if ('sn' in newaddress and 'givenName' in newaddress):
				cn = dn_escape(newaddress['givenName'][0])+' '+dn_escape(newaddress['sn'][0])
				print('new cn=',cn)
			elif ('sn' in newaddress):
				cn = dn_escape(newaddress['sn'][0])
				print('new cn=',cn)
			else:
				cn = dn_escape(newaddress['givenName'][0])
				print('new cn=',cn)
			new_dn = 'cn='+cn+','+tmpaddDN
			for key in all_editable_keys():
				if ((not key in newaddress or len(newaddress[key]) == 0) and key in oldaddress and len(oldaddress[key]) > 0):
					modlist.append((ldap.MOD_DELETE,key,()))
				elif (key in newaddress):
					modlist.append((ldap.MOD_REPLACE, key, self.list_encode(newaddress[key])))
		elif ('o' in newaddress):
			new_dn = 'o='+dn_escape(newaddress['o'][0])+','+tmpaddDN
			for key in all_editable_keys(1):
				if (key != 'o' and (not key in newaddress or len(newaddress[key]) == 0) and key in oldaddress and len(oldaddress[key]) > 0):
#					print('delete',key)
					modlist.append((ldap.MOD_DELETE,key,()))
				elif (key != 'o' and key in newaddress):
#					print('replace',key,newaddress[key])
					modlist.append((ldap.MOD_REPLACE, key, self.list_encode(newaddress[key])))
		else:
			return
		##modlist = self.modlist_encoded(modlist)
		#print('new_dn=',new_dn)
		#print('modlist',modlist)
		try:
			# the dn needs to be a string, the modlist needs to contain bytes
			self.ld.modify_s(dn, modlist)
		except ldap.SERVER_DOWN:
			self.connect(0)
			self.ld.modify_s(dn, modlist)
		if (new_dn != dn):
			new_rdn = ldap.explode_dn(new_dn)[0]
			try:
				self.ld.modrdn_s(dn, new_rdn)
			except ldap.SERVER_DOWN:
				self.connect(0)
				self.ld.modrdn_s(dn, new_rdn)

	def deleteAddress(self,dn):
		try:
			self.ld.delete_s(dn)
		except ldap.SERVER_DOWN:
			self.connect(0)
			self.ld.delete_s(dn)
		except ldap.LDAPError as e:
			print(e)

class EditGui:
	"This class is the gtk gui for the editor"
	
	def getlistforkey(self,key):
		lst = []
		if (not key in self.entry):
			return lst
		j = len(self.entry[key])
		field = field_for_key(key)
		
		for i in range(0,j):
			tmp = self.entry[key][i].get_text()
			if (len(tmp)>0):
				print('append ',tmp)
				lst.append(tmp)
		return lst
	
	def getnewaddress(self):
		newaddress = {}
		for key in all_editable_keys():
			tmp = self.getlistforkey(key)
			if (len(tmp)):
				newaddress[key] = tmp
		return newaddress

	def response(self,data,num):
		if (num == Gtk.ResponseType.CANCEL):
			self.window.destroy()
		elif (num == Gtk.ResponseType.ACCEPT):
			newaddress = self.getnewaddress()
			try:
				if (self.dn):
					self.lb.modifyAddress(self.dn,self.address,newaddress)
				else:
					self.lb.saveAddress(newaddress)
				self.window.destroy()
				return
			except ldap.SERVER_DOWN:
				message = 'Address server was not accessible while trying to save the address'
			except ldap.INSUFFICIENT_ACCESS:
				message = 'Permission was denied while trying to save the address'
			except ldap.ALREADY_EXISTS:
				message = 'This person/organisation exists already'
			dialog = Gtk.MessageDialog(parent=self.window, flags=Gtk.DialogFlags.DESTROY_WITH_PARENT, 
						type=Gtk.MESSAGE_ERROR, buttons=Gtk.BUTTONS_OK,message_format = message)
			dialog.set_title('Error')
			dialog.connect('response', lambda dialog, response: dialog.destroy())
			dialog.show_all()

	def plusclicked(self,widget,key):
		field = field_for_key(key)
		tmp = MyEntry(field.multiline)
		self.entry[key].append(tmp)
		self.vbox[key].pack_start(tmp.widget, False, False, 5)
		tmp.widget.show()
	

	def addfieldentry(self,address,field,table,startat):
		#print('adding ',field.title,field.name,'at startat=',startat)
		table.attach(Gtk.Label(field.title), 0,1,startat, startat+1,xoptions=Gtk.AttachOptions.FILL)
		if (field.editable):
			button = Gtk.Button('+')
			button.connect('clicked', self.plusclicked, field.name)
			self.entry[field.name] = []
			self.vbox[field.name] = Gtk.VBox(True)
			table.attach(self.vbox[field.name], 1,2,startat, startat+1,xoptions=Gtk.AttachOptions.EXPAND|Gtk.AttachOptions.FILL)
			i=0
			if (not field.name in address):
				self.entry[field.name].append(MyEntry(field.multiline))
				self.vbox[field.name].pack_start(self.entry[field.name][i].widget, False, False, 5)
			else:
				j = len(address[field.name])
				for i in range(0,j):
					self.entry[field.name].append(MyEntry(field.multiline))
					print(('i=%d from %d, setting=%s'%(i,j,address[field.name])))
					self.entry[field.name][i].set_text(address[field.name][i])
					self.vbox[field.name].pack_start(self.entry[field.name][i].widget, False, False, 5)
			table.attach(button, 2,3,startat, startat+1,xoptions=Gtk.AttachOptions.FILL,yoptions=0)
		else:
			if (field.name in address):
				table.attach(Gtk.Label(address[field.name][0].decode('utf-8')),1,2,startat, startat+1,xoptions=Gtk.AttachOptions.FILL)
	
	def __init__(self, parentwin, lb, dn, address):
		self.address = address
		self.dn = dn
		self.lb = lb
		self.window = Gtk.Dialog(_('Edit Address'),parentwin,Gtk.DialogFlags.DESTROY_WITH_PARENT,(Gtk.STOCK_CANCEL,Gtk.ResponseType.CANCEL, Gtk.STOCK_OK,Gtk.ResponseType.ACCEPT))
		self.window.set_border_width(6)
		vbox1 = self.window.vbox

		hbox1 = Gtk.HBox(True, True)
		vbox1.pack_start(hbox1, False, False, 5)

		if (dn != None and len(dn)>0 and dn[0] == 'o'):
			isOrg = 1
			numkeys = len(all_editable_keys(1))
		else:
			isOrg = 0
			numkeys = len(LDAP_KEY_MAP)

		self.ltable = Gtk.Table((numkeys/2)+1, 3, False)
		self.ltable.set_row_spacings(6)
		self.ltable.set_col_spacings(6)
		hbox1.pack_start(self.ltable, False, False, 5)
		self.rtable = Gtk.Table((numkeys/2)+1, 3, False)
		self.rtable.set_row_spacings(6)
		self.rtable.set_col_spacings(6)
		hbox1.pack_start(self.rtable, False, False, 5)
		self.entry = {}
		self.vbox = {}
		
		i = 0
		for field in LDAP_KEY_MAP:
#			print(field.name)
			if ((field.inOrg == 1 or field.inOrg == isOrg) and (field.hide != 1 or field.name in address)):
				if (i > numkeys/2):
					self.addfieldentry(address,field,self.rtable,i-numkeys/2)
				else:
					self.addfieldentry(address,field,self.ltable,i)
				i = i + 1
		self.window.connect('response',self.response)
		self.window.show_all()

class ImportEntry:
	n = None
	fn = None
	adr = None
	tel = None
	email = None
	o = None

class ImportCollection:
	collection = []
	emailindex = {}
	fnindex = {}
	telindex = {}

	def __init__(self):
		self.col = []
	
	def printall(self):
		for k in self.col:
			print(k)
	
	def checktruncatednames(self,key,ie1,ie2):
		for i2 in range(0,len(ie2[key])):
			found = False
			for i1 in range(0,len(ie1[key])):
				if (ie1[key][i1] == ie2[key][i2]):
					found = True
					continue
				l1 = len(ie1[key][i1])
				l2 = len(ie2[key][i2])
				minlen = min(l1,l2)
				if (ie1[key][i1][:minlen] == ie2[key][i2][:minlen]):
					found = True
					# keep the longest string, so only change if 2len is longer
					if (l2 > l1):
						print('checktruncatednames, keep', ie2[key][i2], 'and drop',ie1[key][i1])
						ie1[key][i1] = ie2[key][i2]
			if (not found):
				ie1[key].append(ie2[key][i2])
		
	
	def mergeie(self, ie1,ie2):
		added = []
		#print('merging ',ie1,'and',ie2)
		for key in ie2:
			if (key in ie1):
				# check individual values in key
				if (key in ['fn','given','family']):
					self.checktruncatednames(key,ie1,ie2)
				else:
					for i in range(0,len(ie2[key])):
						if (ie2[key][i] not in ie1[key]):
							ie1[key].append(ie2[key][i])
							added.append(ie2[key][i])
			else:
				# copy the complete key
				ie1[key] = ie2[key]
				added.append(ie2[key])
		if (len(added)):
			print('while merging ',ie1,'added',added)
	
	def addnewie(self,ie):
		self.col.append(ie)
		if ('email' in ie):
			for i in range(0,len(ie['email'])):
				self.emailindex[ie['email'][i]] = ie
		if ('tel' in ie):			
			for i in range(0,len(ie['tel'])):
				self.telindex[ie['tel'][i]] = ie
		if ('fn' in ie):
			for i in range(0,len(ie['fn'])):
				self.fnindex[ie['fn'][i]] = ie
	
	def addvcf(self, vcf):
		ie = {}
		have_email = False
		have_tel = False
		have_fn = False
		#print('adding ',vcf)
		for key in vcf.contents.keys():
			#print(key)
			if (key in ['n', 'version', 'photo']):
				continue
			j = len(vcf.contents[key])
			#print('has length',j)
			for i in range(0,j):
				if (not key in ie):
					ie[key] = []
				if (isinstance(vcf.contents[key][i].value, list)):
					value = ''.join(vcf.contents[key][i].value)
				else:
					value = str(vcf.contents[key][i].value)
				if (key == 'tel'):
					value = value.strip('-')
				value = value.strip()
				if (value != ''):
					ie[key].append(value)
					# now check if we have this in the index
					if (key == 'email' and (value in self.emailindex)):
						have_email = value
					if (key == 'tel' and (value in self.telindex)):
						have_tel = value
					if (key == 'fn' and (value in self.fnindex)):
						have_fn = value
		if ('n' in vcf.contents):
			if (not 'family' in ie and vcf.contents['n'][0].value.family!=''):
				ie['family'] = [vcf.contents['n'][0].value.family]
			if (not 'given' in ie and vcf.contents['n'][0].value.given!=''):
				ie['given'] = [vcf.contents['n'][0].value.given]
		
		merge = False
		if (have_fn):
			merge = self.fnindex[have_fn]
		if ((not merge) and have_email):
			merge = self.emailindex[have_email]
		if ((not merge) and have_tel):
			merge = self.telindex[have_tel]
		if (merge):
			self.mergeie(merge, ie)
		else:
			self.addnewie(ie)

	def remove_email_only(self):
		remlist = []
		for i in xrange(len(self.col) - 1, -1, -1):
			keys = self.col[i].keys()
			if ('email' in keys and len(keys)==1):
				print('delete email-only',self.col[i])
				del self.col[i]


class ImportGui:
	lb = None
	dn = None
	imports = []

	
	
	ldapkey = {
		'fn': 'cn',
		'adr':'homePostalAddress',
		'tel':'telephoneNumber',
		'email':'mail',
		'org':'o'
	}

	def __init__(self, parentwin, lb):
		self.lb = lb
		self.parent = parentwin
		#self.window = Gtk.Dialog(_('Import vcf file'),parentwin,Gtk.DIALOG_DESTROY_WITH_PARENT,(Gtk.STOCK_CANCEL,Gtk.ResponseType.CANCEL, Gtk.STOCK_OK,Gtk.RESPONSE_ACCEPT))
		#self.window.set_border_width(6)
		#vbox1 = self.window.vbox
	

	def add_to_address(self,address, vcf, key):
		print(vcf.contents[key])
		j = len(vcf.contents[key])
		if (j==0):
			return address
		ldapkey = self.ldapkey[key]
		#print(('key=%s, ldapkey=%s'%(key,ldapkey)))
		if (not ldapkey in address):
			address[ldapkey] = []
		for i in range(0,j):
			#print(('add %d from %d'%(i,j)))
			print(('%d from %d, append %s to address'%(i,j,vcf.contents[key][i].value)))
			if (isinstance(vcf.contents[key][i].value, list)):
				address[ldapkey].append(''.join(vcf.contents[key][i].value))
			else:
				address[ldapkey].append(str(vcf.contents[key][i].value))
		return address
	
	
	def importvcf(self,filename):
		ic = ImportCollection()
		obj = vobject.readComponents(open(filename))
		for vcf in obj:
			ic.addvcf(vcf)
		ic.remove_email_only()
		ic.printall()
	
	def importvcf_old(self, filename):
		fp = open(filename)
		obj = vobject.readComponents(fp)
		for vcf in obj:
			print(vcf)
			address = self.lb.get_address('cn=test2 surname,ou=Manual,ou=People,o=fakenet')
			print(address)
			#address = {}
			for key in vcf.contents.keys():
				if key in self.ldapkey.keys():
					address = self.add_to_address(address, vcf,key)
			if ('n' in vcf.contents):
				address['sn'] = []
				address['sn'].append(vcf.contents['n'][0].value.family)
				address['givenName'] = []
				address['givenName'].append(vcf.contents['n'][0].value.given)
			print(('after vcs import address=%s'%(address)) )
			ea = EditGui(self.parent,self.lb,'',address)
			#print(vcf)
		fp.close()

class PrefsGui:
	cfg = None
	selected = None
	addressgui = None

	def writeConfig(self):
		fd = open(os.getenv('HOME')+'/.directoryassistant','w')
		self.cfg.write(fd)
		fd.close()
	
	def applyifset(self,entry,section,key,removeempty):
		try:
			tmp = entry.get_text()
			if (removeempty and len(tmp)==0):
				self.cfg.remove_option(section,key)
			else:
#				print('set in config:',section,key,tmp)
				self.cfg.set(section,key,tmp)
		except NoSectionError:
			print('no such section',section)
	
	def apply(self):
		if (self.selected):
			type,key = self.selected
			if (type == 1):
				self.applyifset(self.ldapurl,key,'ldapurl',0)
				self.applyifset(self.base_dn,key,'base_dn',0)
				self.applyifset(self.bind_dn,key,'bind_dn',1)
				self.applyifset(self.bind_password,key,'bind_password',1)
				self.applyifset(self.startup_search,key,'startup_search',1)
				self.applyifset(self.ldapversion,key,'ldapversion',1)
			else:
				self.applyifset(self.action,'DEFAULT','keyaction_'+key,1)
				self.applyifset(self.hide,'DEFAULT','keyhide_'+key,1)
				init_key_map(self.cfg)
		child = self.rframe.get_child()
		if (child):
			child.destroy()

	def okClicked(self,bla1,bla2):
		self.apply()
		self.writeConfig()
		self.addressgui.serverGui()
		self.window.destroy()

	def set_server_entry(self,server,key,entry):
		try:
			entry.set_text(self.cfg.get(server,key))
		except configparser.NoOptionError:
			pass

	def deleteserverclicked(self,button):
		toremove = self.selected[1]
		iter = self.tstore.iter_children(self.serveriter)
		while (iter):
			name, = self.tstore.get(iter,0)
			if (name == toremove):
				break
			iter = self.tstore.iter_next(iter)
		if (not iter):
			return
		self.selected = None
		self.cfg.remove_section(toremove)
		self.tstore.remove(iter)
		if (self.rframe.child):
			self.rframe.child.destroy()
		self.serverToplevelSelected()		

	def serverSelected(self,key):
		table = Gtk.Table(7,2,True)
		table.attach(Gtk.Label(_('Ldap url')),0,1,0,1)
		self.ldapurl = Gtk.Entry()
		table.attach(self.ldapurl,1,2,0,1)
		self.set_server_entry(key,'ldapurl',self.ldapurl)
		
		table.attach(Gtk.Label(_('Base dn')),0,1,1,2)
		self.base_dn = Gtk.Entry()
		table.attach(self.base_dn,1,2,1,2)
		self.set_server_entry(key,'base_dn',self.base_dn)
		
		table.attach(Gtk.Label(_('Bind dn')),0,1,2,3)
		self.bind_dn = Gtk.Entry()
		table.attach(self.bind_dn,1,2,2,3)
		self.set_server_entry(key,'bind_dn',self.bind_dn)
		
		table.attach(Gtk.Label(_('Password')),0,1,3,4)
		self.bind_password = Gtk.Entry()
		table.attach(self.bind_password,1,2,3,4)
		self.set_server_entry(key,'bind_password',self.bind_password)
		
		table.attach(Gtk.Label(_('Default search')),0,1,4,5)
		self.startup_search = Gtk.Entry()
		table.attach(self.startup_search,1,2,4,5)
		self.set_server_entry(key,'startup_search',self.startup_search)
		
		table.attach(Gtk.Label(_('Ldap version')),0,1,5,6)
		self.ldapversion = Gtk.Entry()
		table.attach(self.ldapversion,1,2,5,6)
		self.set_server_entry(key,'ldapversion',self.ldapversion)
	
		but = Gtk.Button('Delete server')
		but.connect('clicked',self.deleteserverclicked)
		table.attach(but,1,2,6,7)
	
		self.rframe.add(table)
		table.show_all()

	def newServerClicked(self,widget,entry):
		name = entry.get_text()
		self.cfg.add_section(name)
		iter = self.tstore.append(self.serveriter)
		self.tstore.set(iter, 0, name, 1,name,2,1)
		selection = self.treev.get_selection()
		self.treev.expand_to_path(self.tstore.get_path(iter))
		selection.select_iter(iter)

	def serverToplevelSelected(self):
		table = Gtk.Table(1,3,True)
		table.attach(Gtk.Label(_('Server name')),0,1,0,1,xoptions=Gtk.AttachOptions.FILL,yoptions=0)
		self.newserver = Gtk.Entry()
		table.attach(self.newserver,1,2,0,1,xoptions=Gtk.AttachOptions.FILL|Gtk.AttachOptions.EXPAND,yoptions=0)
		button = Gtk.Button(Gtk.STOCK_NEW)
		button.set_use_stock(1)
		button.connect('clicked',self.newServerClicked,self.newserver)
		table.attach(button,2,3,0,1,xoptions=Gtk.AttachOptions.FILL,yoptions=0)
		self.rframe.add(table)
		table.show_all()

	def fieldSelected(self,fieldname):
		defs = self.cfg.defaults()
		print(defs)
		table = Gtk.Table(2,2,False)
		
		table.attach(Gtk.Label(_('Action on click')),0,1,0,1,xoptions=Gtk.AttachOptions.FILL)
		self.action = Gtk.Entry()
		table.attach(self.action,1,2,0,1,xoptions=Gtk.AttachOptions.FILL|Gtk.AttachOptions.EXPAND)
		tmp = 'keyaction_'+fieldname
		if (tmp.lower() in defs):
			self.action.set_text(defs[tmp.lower()])
		
		table.attach(Gtk.Label(_('Hide')),0,1,1,2,xoptions=Gtk.AttachOptions.FILL)
		self.hide = Gtk.Entry()
		table.attach(self.hide,1,2,1,2,xoptions=Gtk.AttachOptions.FILL|Gtk.AttachOptions.EXPAND)
		tmp = 'keyhide_'+fieldname
		if (tmp.lower() in defs):
			self.hide.set_text(defs[tmp.lower()])
	
		self.rframe.add(table)
		table.show_all()

	def selectionChanged(self,data):
#		print('selectionChanged')
		self.apply()
		self.selected = None
		store,iter = data.get_selected()
		if (not iter):
			return
		type,key = store.get(iter,2,1)
		if (type == 0):
#			print('toplevel selected')
			self.serverToplevelSelected()
		elif (type == 1):
#			print('server '+key+' selected')
			self.selected = 1,key
			self.serverSelected(key)
		else:
#			print('field '+key+' selected')
			self.selected = 2,key
			self.fieldSelected(key)

	def fillServers(self, piter):
		for sect in self.cfg.sections():
#			print('append',sect,piter)
			iter = self.tstore.append(piter)
			self.tstore.set(iter, 0, sect, 1,sect,2,1)

	def fillFields(self,piter):
		for field in LDAP_KEY_MAP:
			iter = self.tstore.append(piter)
			self.tstore.set(iter, 0, field.title, 1,field.name, 2,2)


	def __init__(self, addressgui,cfg):
		self.cfg = cfg
		self.addressgui = addressgui
		self.window = Gtk.Dialog(_('Directory Assistant Settings'),addressgui.window,Gtk.DialogFlags.DESTROY_WITH_PARENT,(Gtk.STOCK_OK,Gtk.ResponseType.ACCEPT))

		vbox = self.window.vbox
		
		paned = Gtk.HPaned()
		vbox.pack_start(paned, True, True, 5)
		#the left pane
		scrolpane = Gtk.ScrolledWindow()
		self.tstore = Gtk.TreeStore(str, str, int)

		self.serveriter = self.tstore.append(None)
		self.tstore.set(self.serveriter, 0, _('Servers'), 2,0)
		self.fillServers(self.serveriter)

		self.fielditer = self.tstore.append(None)
		self.tstore.set(self.fielditer,  0,_('Fields'), 2,0)
		self.fillFields(self.fielditer)

		self.treev = Gtk.TreeView(self.tstore)
		scrolpane.set_size_request(150,200)
		rend = Gtk.CellRendererText()
		column = Gtk.TreeViewColumn(None, rend, text=0)
		self.treev.append_column(column)
		selection = self.treev.get_selection()
		selection.set_mode(Gtk.SelectionMode.SINGLE)
		selection.connect('changed', self.selectionChanged)
		scrolpane.add_with_viewport(self.treev)
		paned.add1(scrolpane)
		#the right pane
		self.rframe = Gtk.Frame()
		self.rframe.set_shadow_type(Gtk.ShadowType.IN)
		self.rframe.add(Gtk.Label())
		paned.add2(self.rframe)

		self.window.set_default_size(300,-1)
		self.window.show_all()
		self.window.connect('response',self.okClicked)


class AddressGui:
	"This class is the GTK GUI for the main window"
	dn = None
	address = None
	lb = None
	cfg = None
	ldservers = None
	
	def delete_event(self, widget, data):
		return False
		
	def destroy(self, widget, data=None):
		Gtk.main_quit()
	
	def treevClicked(self,treev,event):
		if (event.type == 4):
			# 4 seems to be single-click, so we refresh
			if (self.dn != None):
				self.address = self.lb.get_address(self.dn)
				self.set_address_label(self.address)
		# 5 seems to be doubleclick, and 6 tripleclick
		if (event.type >= 5):
			if (self.dn):
				ea = EditGui(self.window,self.lb,self.dn,self.address)
	
	def newClicked(self,bla):
		ea = EditGui(self.window,self.lb,'',{})

	def importClicked(self,bla):
		diag = Gtk.FileChooserDialog(title="Import",parent=self.window,action=Gtk.FileChooserAction.OPEN,buttons=('Cancel',Gtk.ResponseType.CANCEL,'Open',Gtk.ResponseType.OK))
		resp = diag.run()
		if (resp == Gtk.ResponseType.OK):
			importfile = diag.get_filename()
			print(('selected %s'%(importfile)))
			ig = ImportGui(self.window, self.lb) 
			ig.importvcf(importfile)
		diag.destroy()
	
	def deleteClicked(self,bla):
		if (self.dn != None):
			self.lb.deleteAddress(self.dn)
			self.dn = None
			self.address = None
			self.searchClicked(None)	

	def get_all(self,entry,prefix,suffix):
		str = ''
		i=len(entry)-1
#		print('get_all, i=',i)
		if (i >= 0):
			while (i >= 0):
				str += prefix+self.prepare(entry[i])+suffix
				i -= 1
		return str
	
	def prepare(self,mystr):
		if (isinstance(mystr, bytes)):
			mystr = mystr.decode('utf-8')
		#mystr = str(mystr)
		mystr = mystr.replace('&', '&amp;')
		mystr = mystr.replace('<', '&lt;')
		mystr = mystr.replace('>', '&gt;')
		return mystr
	
	def set_warning_label(self,message):
		if (self.rpanetable != None):
			self.rpanetable.destroy()
			self.rpanetable = None
		self.wlabel = Gtk.Label()
		self.wlabel.set_markup(message)
		self.rframe.add(self.wlabel)
		self.wlabel.show()
	
	def label_clicked(self,widget,event,fieldname):
		f = field_for_key(fieldname)
		tmp = f.action % widget.child.get_text()
#		print('tmp=',tmp)
		os.system(tmp)
	
	def create_label(self,vbox,fieldname,text):
		label = Gtk.Label()
		
		label.set_alignment(0,0)
		label.set_justify(Gtk.Justification.LEFT)
		f = field_for_key(fieldname)
		if (f.action != None):
			label.set_markup('<u>'+text+'</u>')
			evbox = Gtk.EventBox()
			evbox.connect("button-press-event", self.label_clicked,fieldname)
			evbox.add(label)
			vbox.pack_start(evbox, False, False, 5)
			evbox.realize()
			hand = Gtk.gdk.Cursor(Gtk.gdk.HAND1)
			evbox.window.set_cursor(hand)
		else:
			label.set_selectable(True)
			label.set_markup(''+self.prepare(text)+'')
			vbox.pack_start(label, False, False, 5)

	def set_address_label(self,entry):
		if (self.wlabel):
			self.wlabel.destroy()
			self.wlabel = None
		else:
			self.rpanetable.destroy()
#		self.label = Gtk.Label()
#		self.label.set_size_request(250,200)
#		self.label.set_selectable(True)
		self.rpanetable = Gtk.Table(len(LDAP_KEY_MAP),2,False)
		self.rpanetable.set_col_spacings(5)
		self.rframe.add(self.rpanetable)
		i = 0
		for field in LDAP_KEY_MAP:
#			print('include field',field.name)
			if (field.name in entry):
				label = Gtk.Label()
				label.set_markup('<b><small>'+field.title+'</small></b>')
				label.set_alignment(0,0)
				self.rpanetable.attach(label, 0,1,i, i+1,xoptions=Gtk.AttachOptions.FILL,yoptions=Gtk.AttachOptions.FILL)
				vbox = Gtk.VBox()
				self.rpanetable.attach(vbox, 1,2,i, i+1,xoptions=Gtk.AttachOptions.FILL,yoptions=0)
				j=0
				while (j < len(entry[field.name])):
					self.create_label(vbox,field.name,entry[field.name][j])
					j += 1
				i = i + 1
#			else:
#				print('ignore field',field.name)
		self.rpanetable.show_all()

	def searchClicked(self, bla):
		if (self.ldservers != None):
			server = self.ldservers.get_active_text()
			if (self.lb.name != server):
#				print('switch to new server '+server)
				self.lb = LdapBackend(self.cfg,server,0)
		self.listm.clear()
		str = self.entry.get_text()
		try:
			tmp1 = self.lb.get_dnlist_name(str)
			iter = None
			for k, v in tmp1.items():
				iter = self.listm.prepend()
				#print('prepending '+k+' with v='+v.decode('utf-8'))
				self.listm.set(iter, 0, v.decode('utf-8'), 1, k)
			if (iter != None):
				iter = self.listm.get_iter_first()
				selection = self.treev.get_selection()
				selection.select_iter(iter)
				self.treev.grab_focus()
		except ldap.SERVER_DOWN:
			self.set_warning_label('<span foreground="red"><b>'+_('address server not available')+'</b></span>')
	
	def closeClicked(self,bla):
		self.window.destroy()

	def prefsClicked(self,bla):
		pg = PrefsGui(self,self.cfg)

	def selectionChanged(self, data):
		store = data.get_selected()[0]
		iter = data.get_selected()[1]
		if (iter == None):
			return
		try:
			self.dn = store.get_value(iter,1)
			self.address = self.lb.get_address(self.dn)
			self.set_address_label(self.address)
		except ldap.SERVER_DOWN:
			self.dn = None
			self.address = None
			self.set_warning_label("no result")

	def serverGui(self):
		if (self.ldservers != None):
			self.ldservers.destroy()
		#print('have ', len(self.cfg.sections()), 'sections')
		if (len(self.cfg.sections()) >1):
			label = Gtk.Label(_("in address book"))
			self.table.attach(label,0,1,1,2)
			self.ldservers = Gtk.combo_box_new_text()
			lst = self.cfg.sections()
			lst.reverse()
			for sect in lst:
				if (self.lb == None):
					self.lb = LdapBackend(self.cfg,sect,0)
				self.ldservers.append_text(sect)
			self.ldservers.set_active(0)
			self.table.attach(self.ldservers,1,2,1,2)
			self.table.show_all()
		else:
			self.ldservers = None
			for sect in self.cfg.sections():
				self.lb = LdapBackend(self.cfg,sect,0)
				break

	def __init__(self):
		self.cfg = configparser.ConfigParser()
		self.cfg.read(('/etc/directoryassistant', os.getenv('HOME')+'/.directoryassistant'))
		init_key_map(self.cfg)
		
		self.window = Gtk.Window(Gtk.WindowType.TOPLEVEL)
		self.window.set_title(_("Directory Assistant ")+VERSIONSTRING)
		self.window.set_role('directoryassistant')
		self.window.set_border_width(10)
		#pixbuf = Gtk.gdk.pixbuf_new_from_file(DATADIR+'logo.svg')
		#Gtk.window_set_default_icon(pixbuf)
		self.window.connect('delete_event', self.delete_event)
		self.window.connect('destroy', self.destroy)
		self.window.set_border_width(10)
		vbox = Gtk.VBox(False,10)
		self.window.add(vbox)
		# the search toolbar
		hbox = Gtk.HBox()
		vbox.pack_start(hbox, False, True, 5)
		#try:
		image = Gtk.Image()
		image.set_from_file(DATADIR+'logo.svg')
		hbox.pack_start(image, False, False, 5)
		#except gobject.GError:
		#	pass
		self.table = Gtk.Table(2, 2, False)
		self.table.set_row_spacings(10)
		self.table.set_col_spacings(10)
		hbox.pack_start(self.table, False, False, 5)

		label = Gtk.Label(_("Search for"))
		self.table.attach(label,0,1,0,1)
		self.entry = Gtk.Entry()
		self.entry.connect("activate", self.searchClicked)
		self.table.attach(self.entry,1,2,0,1)
		self.serverGui()
		# the paned
		paned = Gtk.HPaned()
		vbox.pack_start(paned, True, True, 5)
		#the left pane
		scrolpane = Gtk.ScrolledWindow()
		scrolpane.set_size_request(150,200)
		scrolpane.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
		
		self.listm = Gtk.ListStore(str,str)
		self.listm.set_sort_column_id(1, Gtk.SortType.ASCENDING)
		self.treev = Gtk.TreeView(self.listm)
		
		rend = Gtk.CellRendererText()
		column = Gtk.TreeViewColumn(_('Results'), rend, text=0)
		self.treev.append_column(column)
		selection = self.treev.get_selection()
		selection.set_mode(Gtk.SelectionMode.SINGLE)
		selection.connect('changed', self.selectionChanged)
		self.treev.connect('button_press_event',self.treevClicked)
		scrolpane.add(self.treev)
		paned.add1(scrolpane)
		#the right pane
		self.rframe = Gtk.Frame()
		self.rframe.set_shadow_type(Gtk.ShadowType.IN)
		self.rpanetable = None
#		self.label = Gtk.Label()
#		self.label.set_size_request(250,200)
#		self.label.set_selectable(True)
		self.set_warning_label(_("no results yet"));
#		self.rframe.add(self.label)
		paned.add2(self.rframe)
		# buttonbox
		bbox = Gtk.HButtonBox()
		bbox.set_layout(Gtk.ButtonBoxStyle.END)
		bbox.set_spacing(10)

		button = Gtk.Button(Gtk.STOCK_PREFERENCES)
		button.set_use_stock(1)
		bbox.add(button)
		button.connect('clicked', self.prefsClicked)

		button = Gtk.Button(Gtk.STOCK_NEW)
		button.set_use_stock(1)
		bbox.add(button)
		button.connect('clicked', self.newClicked)

		button = Gtk.Button('Import')
		button.set_use_stock(1)
		bbox.add(button)
		button.connect('clicked', self.importClicked)

		button = Gtk.Button(Gtk.STOCK_DELETE)
		button.set_use_stock(1)
		bbox.add(button)
		button.connect('clicked', self.deleteClicked)

		button = Gtk.Button(Gtk.STOCK_CLOSE)
		button.connect('clicked', self.closeClicked)
		button.set_use_stock(1)
		bbox.add(button)

		button = Gtk.Button(Gtk.STOCK_FIND)
		button.set_use_stock(1)
		button.connect('clicked',self.searchClicked)
		bbox.add(button)

		#button.set_flags(Gtk.CAN_DEFAULT)
		self.window.set_default(button)

		vbox.pack_start(bbox,False,True,5)
		self.window.show_all()
		if (self.lb != None):
			try:
				self.entry.set_text(self.cfg.get(self.lb.name, 'startup_search'))
				self.searchClicked(None)
			except configparser.NoOptionError:
				pass
		self.entry.grab_focus()

	def connectldap(self):
		self.lb.connect(0)
		return 0

	def main(self):
		GObject.idle_add(self.connectldap)
		Gtk.main()

class ErrorMessage:
	def quit(self, wid, data):
		Gtk.main_quit()
	def __init__(self, message):
		dialog = Gtk.Dialog(title="ERROR", flags=Gtk.DIALOG_MODAL, buttons=(Gtk.STOCK_OK, 1))
		label = Gtk.Label()
		label.set_markup('<b>'+message+'</b>')
		dialog.vbox.pack_start(label, True, True, 0)
		dialog.connect('close',self.quit)
		dialog.connect('response',self.quit)
		dialog.show_all()

		Gtk.main()

if __name__ == "__main__":
	gettext.install('directoryassistant','/usr/share/locale')
	ag = AddressGui()
	ag.main()
