sogo/Migration/Horde/TurbaConverter.py

352 lines
11 KiB
Python

import PHPDeserializer
import webdavlib
import sys
commonMappings = { "owner_id": "owner",
"object_id": "filename",
"object_uid": "uid",
"object_name": "fn" }
cardMappings = { "object_alias": "nickname",
"object_email": "email",
"object_homeaddress": "homeaddress",
"object_homephone": "homephone",
"object_workaddress": "workaddress",
"object_workphone": "workphone",
"object_cellphone": "cellphone",
"object_fax": "fax",
"object_title": "title",
"object_company": "org",
"object_notes": "notes",
"object_freebusyurl": "fburl" }
prodid = "-//Inverse inc.//SOGo Turba Importer 1.0//EN"
# a managed type of template where each line is put only if at least one field
# has been filled
cardTemplate = u"""BEGIN:VCARD\r
VERSION:3.0\r
PRODID:%s\r
UID:${uid}\r
FN:${fn}\r
TITLE:${title}\r
ORG:${org};\r
NICKNAME:${nickname}\r
EMAIL:${email}\r
ADR;TYPE=work:;;${workaddress};;;;\r
ADR;TYPE=home:;;${homeaddress};;;;\r
TEL;TYPE=work:${workphone}\r
TEL;TYPE=home:${homephone}\r
TEL;TYPE=fax:${fax}\r
NOTE:${notes}\r
FBURL:${fburl}\r
END:VCARD""" % prodid
class TurbaConverter:
def __init__(self, user, webdavConfig):
self.user = user
self.webdavConfig = webdavConfig
def start(self, conn):
self.conn = conn
self.readUsers()
self.missing = []
for user in self.users.keys():
self.hasCards = False
self.hasLists = False
self.currentUser = user
self.readUserRecords()
if self.hasCards or self.hasLists:
print "Converting addressbook of '%s'" % user
self.prepareCards()
self.uploadCards()
self.prepareLists()
self.uploadLists()
else:
self.missing.append(user)
if len(self.missing) > 0:
print "No information extracted for: %s" % ", ".join(self.missing)
print "Done"
def readUsers(self):
self.users = {}
cursor = self.conn.cursor()
query = "SELECT user_uid, datatree_name FROM horde_datatree"
if self.user != "ALL":
query = query + " WHERE user_uid = '%s'" % self.user
cursor.execute(query)
records = cursor.fetchall()
count = 0
max = len(records)
for record in records:
record_user = record[0].lower()
if not self.users.has_key(record_user):
self.users[record_user] = []
self.users[record_user].append(record[1])
count = count + 1
cursor.close()
def readUserRecords(self):
self.cards = {}
self.lists = {}
cursor = self.conn.cursor()
owner_ids = self.users[self.currentUser]
whereClause = "owner_id = '%s'" % "' or owner_id = '".join(owner_ids)
query = "SELECT * FROM turba_objects WHERE %s" % whereClause
cursor.execute(query)
self.prepareColumns(cursor)
records = cursor.fetchall()
count = 0
max = len(records)
while count < max:
self.parseRecord(records[count])
count = count + 1
cursor.close()
def prepareColumns(self, cursor):
self.columns = {}
count = 0
for dbColumn in cursor.description:
columnId = dbColumn[0]
self.columns[columnId] = count
count = count + 1
def parseRecord(self, record):
typeCol = self.columns["object_type"]
meta = {}
self.applyRecordMappings(meta, record, commonMappings)
if record[typeCol] == "Object":
meta["type"] = "card"
self.hasCards = True
self.applyRecordMappings(meta, record, cardMappings)
elif record[typeCol] == "Group":
meta["type"] = "list"
self.hasLists = True
self.fillListMembers(meta, record)
else:
raise Exception, "UNKNOWN TYPE: %s" % record[type]
self.dispatchMeta(meta)
def applyRecordMappings(self, meta, record, mappings):
for k in mappings.keys():
metaKey = mappings[k]
meta[metaKey] = self.recordColumn(record, k)
def recordColumn(self, record, columnName):
columnIndex = self.columns[columnName]
value = record[columnIndex]
if value is None:
value = u""
else:
value = self.deUTF8Ize(value)
return value
def deUTF8Ize(self, value):
# unicode -> repeat(utf-8 str -> iso-8859-1 str) -> unicode
oldValue = value
done = False
while not done:
try:
utf8Value = value.encode("iso-8859-1")
value = utf8Value.decode("utf-8")
except:
done = True
if value == oldValue:
done = True
return value
def fillListMembers(self, meta, record):
members = self.recordColumn(record, "object_members")
if members is not None and len(members) > 0:
deserializer = PHPDeserializer.PHPDeserializer(members)
dMembers = deserializer.deserialize()
else:
dMembers = []
meta["members"] = dMembers
def dispatchMeta(self, meta):
owner = meta["owner"]
if meta["type"] == "card":
repository = self.cards
else:
repository = self.lists
filename = meta["filename"]
repository[filename] = meta
def prepareCards(self):
count = 0
for filename in self.cards.keys():
card = self.cards[filename]
card["data"] = self.buildVCard(card).encode("utf-8")
count = count + 1
if count > 0:
print " prepared %d cards" % count
def buildVCard(self, card):
vcardArray = []
tmplArray = cardTemplate.split("\r\n")
for line in tmplArray:
keyPos = line.find("${")
if keyPos > -1:
keyEndPos = line.find("}")
key = line[keyPos+2:keyEndPos]
if card.has_key(key):
value = card[key]
if len(value) > 0:
newLine = "%s%s%s" % (line[0:keyPos],
value.replace(";", "\;"),
line[keyEndPos + 1:])
vcardArray.append(self.foldLineIfNeeded(newLine))
else:
vcardArray.append(self.foldLineIfNeeded(line))
return "\r\n".join(vcardArray)
def foldLineIfNeeded(self, line):
wasFolded = False
newLine = line\
.replace("\\", "\\\\") \
.replace("\r", "\\r") \
.replace("\n", "\\n")
lines = []
while len(newLine) > 73:
wasFolded = True
lines.append(newLine[0:73])
newLine = newLine[73:]
lines.append(newLine)
newLine = "\r\n ".join(lines)
if wasFolded:
print "line was folded: '%s' ->\n\n%s\n\n" % (line, newLine)
return newLine
def uploadCards(self):
self.uploadEntries(self.cards,
"vcf", "text/x-vcard; charset=utf-8");
def prepareLists(self):
count = 0
skipped = 0
for filename in self.lists.keys():
list = self.lists[filename]
vlist = self.buildVList(list)
if vlist is None:
skipped = skipped + 1
else:
list["data"] = vlist.encode("utf-8")
count = count + 1
if (count + skipped) > 0:
print " prepared %d lists. %d were skipped." % (count, skipped)
def buildVList(self, list):
vlist = None
members = list["members"]
if len(members) > 0:
cardMembers = []
for member in members:
card = self.getListCard(member)
if card is not None:
cardMembers.append(card)
if len(cardMembers) > 0:
vlist = self.assembleVList(list, cardMembers)
else:
print " list '%s' skipped because of lack of usable" \
" members" % list["filename"]
return vlist
def getListCard(self, cardRef):
card = None
if len(cardRef) != 0 and not cardRef.startswith("localldap:"):
if cardRef.startswith("localsql:"):
cardRef = cardRef[9:]
if self.cards.has_key(cardRef):
card = self.cards[cardRef]
else:
print "card reference does not exist: '%s'" % cardRef
return card
def assembleVList(self, list, cardMembers):
entries = []
for cardMember in cardMembers:
if cardMember.has_key("fn") and len(cardMember["fn"]) > 0:
fn = ";FN=%s" % cardMember["fn"]
else:
fn = ""
if cardMember.has_key("email") and len(cardMember["email"]) > 0:
email = ";EMAIL=%s" % cardMember["email"]
else:
email = ""
entries.append("CARD%s%s:%s.vcf"
% (fn, email, cardMember["filename"]))
if list.has_key("fn") and len(list["fn"]) > 0:
listfn = "FN:%s\r\n" % list["fn"]
else:
listfn = ""
vlist = """BEGIN:VLIST\r
PRODID:%s\r
VERSION:1.0\r
UID:%s\r
%s%s\r
END:VLIST""" % (prodid, list["uid"], listfn, "\r\n".join(entries))
return vlist
def uploadLists(self):
self.uploadEntries(self.lists,
"vlf", "text/x-vcard; charset=utf-8");
def uploadEntries(self, entries, extension, mimeType):
isatty = sys.stdout.isatty() # enable progressive display of summary
success = 0
failure = 0
client = webdavlib.WebDAVClient(self.webdavConfig["hostname"],
self.webdavConfig["port"],
self.webdavConfig["username"],
self.webdavConfig["password"])
collection = '/SOGo/dav/%s/Contacts/personal' % self.currentUser
mkcol = webdavlib.WebDAVMKCOL(collection)
client.execute(mkcol)
for entryName in entries.keys():
entry = entries[entryName]
if entry.has_key("data"):
fullFilename = "%s.%s" % (entry["filename"], extension)
url = "%s/%s" % (collection, fullFilename)
put = webdavlib.HTTPPUT(url, entry["data"])
put.content_type = mimeType
client.execute(put)
if (put.response["status"] < 200
or put.response["status"] > 399):
failure = failure + 1
print " error uploading '%s': %d" \
% (fullFilename, put.response["status"])
else:
success = success + 1
if isatty:
print "\r successes: %d; failures: %d" % (success, failure),
if (success + failure) % 5 == 0:
sys.stdout.flush()
if isatty:
print ""
else:
if (success + failure) > 0:
print " successes: %d; failures: %d\n" % (success, failure)
sys.stdout.flush()