[kupfer: 11/23] plugin.thunderbird: +thunderbird_support
- From: Ulrik Sverdrup <usverdrup src gnome org>
- To: svn-commits-list gnome org
- Cc:
- Subject: [kupfer: 11/23] plugin.thunderbird: +thunderbird_support
- Date: Sat, 2 Jan 2010 23:11:13 +0000 (UTC)
commit aef8750d932c8a6666a0ccc306455fb1cb5d7d0a
Author: Karol BÄ?dkowski <karol bedkowsk+gh gmail com>
Date: Sun Dec 13 00:11:54 2009 +0100
plugin.thunderbird: +thunderbird_support
kupfer/plugin/thunderbird_support.py | 224 ++++++++++++++++++++++++++++++++++
1 files changed, 224 insertions(+), 0 deletions(-)
---
diff --git a/kupfer/plugin/thunderbird_support.py b/kupfer/plugin/thunderbird_support.py
new file mode 100644
index 0000000..6a2cc76
--- /dev/null
+++ b/kupfer/plugin/thunderbird_support.py
@@ -0,0 +1,224 @@
+# -*- coding: UTF-8 -*-
+
+from __future__ import with_statement
+
+import os
+import re
+from ConfigParser import RawConfigParser
+
+__version__ = "2009-12-11"
+__author__ = "Karol BÄ?dkowski <karol bedkowski gmail com>"
+
+'''
+Module provide function to read Thunderbird's address book.
+
+Concept for mork parser from:
+ - demork.py by Kumaran Santhanam
+ - mork.cs from GnomeDo by Pierre Ã?stlund
+'''
+
+RE_COLS = re.compile(r'<\s*<\(a=c\)>\s*(\/\/)?\s*(\(.+?\))\s*>')
+RE_CELL = re.compile(r'\((.+?)\)')
+RE_ATOM = re.compile(r'<\s*(\(.+?\))\s*>')
+RE_TABLE = re.compile(
+ r'\{-?(\d+):\^(..)\s*\{\(k\^(..):c\)\(s=9u?\)\s*(.*?)\}\s*(.+?)\}')
+RE_ROW = re.compile(r'(-?)\s*\[(.+?)((\(.+?\)\s*)*)\]')
+RE_CELL_TEXT = re.compile(r'\^(.+?)=(.*)')
+RE_CELL_OID = re.compile(r'\^(.+?)\^(.+)')
+RE_ESCAPED = re.compile(r'((\\[\$\0abtnvfr])|(\$..))')
+
+COLS_TO_KEEP = (
+ 'DisplayName',
+ 'FirstName',
+ 'LastName',
+ 'PrimaryEmail',
+ 'SecondEmail'
+)
+
+class _Table(object):
+ def __init__(self, tableid):
+ self.tableid = tableid
+ self.rows = {}
+
+ def __repr__(self):
+ return 'Table %r: %r' % (self.tableid, self.rows)
+
+ def add_cell(self, rowid, col, atom):
+ row = self.rows.get(rowid)
+ if not row:
+ row = self.rows[rowid] = dict()
+ row[col] = _unescape_data(atom)
+
+SPECIAL_CHARS = (
+ ('\\\\', '\\'),
+ ('\\$', '$'),
+ ('\\0', chr(0)),
+ ('\\a', chr(7)),
+ ('\\b', chr(8)),
+ ('\\t', chr(9)),
+ ('\\n', chr(10)),
+ ('\\v', chr(11)),
+ ('\\f', chr(12)),
+ ('\\r', chr(13)),
+)
+
+def _unescape_data(instr):
+ for src, dst in SPECIAL_CHARS:
+ instr = instr.replace(src, dst)
+ return RE_ESCAPED.sub(lambda x:chr(int(x.group()[1:], 16)), instr)
+
+
+def _read_mork(filename):
+ ''' Read mork file, return tables from file '''
+ data = []
+ with open(filename, 'rt') as mfile:
+ version = mfile.readline().strip()
+ # check header
+ if not re.match(r'// <!-- <mdb:mork:z v="(.*)"/> -->', version):
+ print 'header error'
+ return {}
+
+ for line in mfile.readlines():
+ # remove blank lines and comments
+ line = line.strip()
+ if not line or line.startswith('//'):
+ continue
+
+ # remove comments on the end of lines
+ comments = line.find('//')
+ if comments > -1:
+ line = line[:comments].strip()
+
+ if line:
+ data.append(line)
+
+ data = ''.join(data)
+
+ if not data:
+ return {}
+
+ # decode data
+ cells = {}
+ atoms = {}
+ tables = {}
+ pos = 0
+ while data:
+ data = data[pos:].lstrip()
+ if not data:
+ break
+
+ # cols
+ match = RE_COLS.match(data)
+ if match:
+ for cell in RE_CELL.findall(match.group()):
+ key, val = cell.split('=', 1)
+ if val in COLS_TO_KEEP: # skip necessary columns
+ cells[key] = val
+
+ pos = match.span()[1]
+ continue
+
+ # atoms
+ match = RE_ATOM.match(data)
+ if match:
+ for cell in RE_CELL.findall(match.group()):
+ key, val = cell.split('=', 1)
+ atoms[key] = val
+
+ pos = match.span()[1]
+ continue
+
+ # tables
+ match = RE_TABLE.match(data)
+ if match:
+ tableid = ':'.join(match.group()[1:2])
+ table = tables.get(tableid)
+ if not table:
+ table = tables[tableid] = _Table(match.group(1))
+
+ for row in RE_ROW.findall(match.group()):
+ tran, rowid = row[:2]
+ if tran != '-':
+ rowdata = row[2:]
+ for rowcell in rowdata:
+ for cell in RE_CELL.findall(rowcell):
+ match = (RE_CELL_TEXT.match(cell) or \
+ RE_CELL_OID.match(cell))
+ if match:
+ col = cells.get(match.group(1))
+ atom = atoms.get(match.group(2))
+ if col and atom:
+ table.add_cell(rowid, col, atom)
+ continue
+
+ pos += match.span()[1]
+ continue
+
+ pos += 1
+ return tables
+
+
+def _mork2contacts(tables):
+ ''' Get contacts from mork table prepared by _read_mork '''
+ if not tables:
+ return
+
+ for row in tables.values()[0].rows.itervalues():
+ display_name = row.get('DisplayName')
+ if not display_name:
+ first_name = row.get('FirstName', '')
+ last_name = row.get('LastName', '')
+ display_name = ' '.join((first_name, last_name))
+
+ display_name = display_name.strip()
+ if not display_name:
+ continue
+
+ for key in ('PrimaryEmail', 'SecondEmail'):
+ email = row.get(key)
+ if email:
+ yield (display_name, email)
+
+
+def get_thunderbird_addressbook_file():
+ profile_file = os.path.expanduser('~/.thunderbird/profiles.ini')
+ if not os.path.isfile(profile_file):
+ return None
+
+ config = RawConfigParser()
+ config.read(profile_file)
+ path = None
+ for section in config.sections():
+ if config.has_option(section, "Default") and \
+ config.get(section, "Default") == "1":
+ path = config.get(section, "Path")
+ break
+ elif config.has_option(section, "Path"):
+ path = config.get(section, "Path")
+
+ if path:
+ path = os.path.join(os.path.expanduser('~/.thunderbird'), path,
+ 'abook.mab')
+ if not os.path.isfile(path):
+ return None
+ return path
+
+
+
+def get_contacts():
+ ''' Get all contacts from Thunderbird address book as
+ [(contact name, contact email)] '''
+ abook = get_thunderbird_addressbook_file()
+ if abook:
+ try:
+ tables = _read_mork(abook)
+ except IOError, err:
+ print err
+ else:
+ return list(_mork2contacts(tables))
+
+ return []
+
+
+if __name__ == '__main__':
+ print get_contacts()
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]