PyXR

c:\projects\bitpim\src \ native \ evolution \ evolution.py



0001 ### BITPIM
0002 ###
0003 ### Copyright (C) 2004 Roger Binns <rogerb@rogerbinns.com>
0004 ### Copyright (C) 2004 Peter Pletcher <peterpl@pacbell.net>
0005 ###
0006 ### This program is free software; you can redistribute it and/or modify
0007 ### it under the terms of the BitPim license as detailed in the LICENSE file.
0008 ###
0009 ### $Id: evolution.py 1442 2004-07-14 05:14:24Z rogerb $
0010 
0011 "Be at one with Evolution"
0012 
0013 # Evolution mostly sucks when compared to Outlook.  The UI and functionality
0014 # for the address book is a literal copy.  There is no API as such and we
0015 # just have to delve around the filesystem
0016 
0017 # root directory is ~/evolution
0018 # folders are any directory containing a file named folder-metadata.xml
0019 # note that folders can be nested
0020 #
0021 # the folder name is the directory name.  The folder-metadata.xml file
0022 # does contain description tag, but it isn't normally displayed and
0023 # is usually empty for user created folders
0024 #
0025 # if the folder contains any addressbook entries, then there will
0026 # be an addressbook.db file
0027 #
0028 # the file should be opened using bsddb
0029 # import bsddb
0030 # db=bsddb.hashopen("addressbook.db", "r")
0031 # db.keys() lists keys, db[key] gets item
0032 #
0033 # the item contains exactly one field which is a null terminated string
0034 # containing a vcard
0035 
0036 
0037 import sys
0038 import os
0039 import re
0040 
0041 if sys.platform!="linux2":
0042     raise ImportError()
0043 
0044 try:
0045     import bsddb
0046 except:
0047     raise ImportError()
0048 
0049 
0050 userdir=os.path.expanduser("~")
0051 evolutionpath="evolution/local"
0052 evolutionbasedir=os.path.join(userdir, evolutionpath)
0053 evolutionexporter = {
0054     'command'    : "evolution-addressbook-export",
0055     'folderid'   : "<Evolution Addressbook Exporter>",
0056     'name'       : "<Evolution Addressbook Exporter>",
0057     'type'       : ["address book"]
0058 }
0059 
0060 def getcontacts(folder):
0061     """Returns the contacts as a list of string vcards
0062 
0063     Note that the Windows EOL convention is used"""
0064 
0065     if folder == evolutionexporter['folderid']:
0066         return getcontacts_evoexporter()
0067     
0068     dir=os.path.expanduser(folder)
0069     p=os.path.join(dir, "addressbook.db")
0070     if not os.path.isfile(p):
0071         # ok, this is not an address book folder
0072         if not os.path.isfile(os.path.join(dir, "folder-metadata.xml")):
0073             raise ValueError("Supplied folder is not a folder! "+folder)
0074         raise ValueError("Folder does not contain contacts! "+folder)
0075     res=[]
0076     db=bsddb.hashopen(p, 'r')
0077     for key in db.keys():
0078         if key.startswith("PAS-DB-VERSION"): # no need for this field
0079             continue
0080         data=db[key]
0081         while data[-1]=="\x00": # often has actual null on the end
0082             data=data[:-1]  
0083         res.append(data)
0084     db.close()
0085     return res
0086 
0087 class EvolutionExportException(Exception):
0088     pass
0089 
0090 def getcontacts_evoexporter():
0091     """Get the cards by running evolution-addressbook-export
0092 
0093     Note that this code returns all the contacts as a single
0094     string item.  It seemed silly to split them apart when
0095     the caller then just puts them back together as one
0096     string"""
0097     evo_export = os.popen(evolutionexporter['command'])
0098     evo_cards = evo_export.read()
0099     evo_export_status = evo_export.close()
0100     if evo_export_status is not None:
0101         raise EvolutionExportException("%s failed with code %s" % (evolutionexporter['command'], `evo_export_status`))
0102     return [evo_cards]
0103 
0104 def getfsfolders(basedir=evolutionbasedir):
0105 
0106     res={}
0107     children=[]
0108 
0109     for f in os.listdir(basedir):
0110         p=os.path.join(basedir, f)
0111 
0112         # deal with child folders (depth first)
0113         if os.path.isdir(p):
0114             f=getfsfolders(p)
0115             if len(f):
0116                 children.extend(f)
0117             continue
0118 
0119     # if we have any children, sort them
0120     if len(children):
0121         lc=[ (child['name'], child) for child in children] # decorate
0122         lc.sort() # sort
0123         children=[child for _, child in lc] # un-decorate
0124 
0125 
0126     # do we have a meta-data file?
0127     if not os.path.isfile(os.path.join(basedir, "folder-metadata.xml")):
0128         return children
0129 
0130     # ok, what type is this folder
0131     t=[]
0132     for file,type in ( ("mbox", "mailbox"), ("calendar.ics", "calendar"), ("addressbook.db", "address book"),
0133                                ("tasks.ics", "tasks") ):
0134         if os.path.isfile(os.path.join(basedir, file)):
0135             t.append(type)
0136 
0137     entry={}
0138     entry['dirpath']=basedir
0139     entry['name']=os.path.basename(basedir)
0140     entry['folderid']=basedir.replace(userdir, "~", 1)
0141     entry['type']=t
0142     if len(children):
0143         entry['children']=children
0144         # tell children who their daddy is
0145         for c in children:
0146             c['parent']=entry
0147         
0148     return [entry]
0149 
0150 def getspecialfolders():
0151     "Return a list of any special folders"
0152 
0153     # the only one we look for currently is evolution-addressbook-export
0154     # command
0155     evo_version = os.popen(evolutionexporter['command'] + " --version")
0156     evo_version_result = evo_version.read()
0157     evo_version_status = evo_version.close()
0158     if evo_version_status is not None:
0159         return []
0160     # it doesn't work with earlier versions of evolution, so we do a version
0161     # check
0162     if evo_version_result.startswith("Gnome evolution 1.4"):
0163         return [evolutionexporter]
0164     else:
0165         return []
0166 
0167 def getfolders():
0168     return getspecialfolders()+getfsfolders()
0169 
0170 def pickfolder(selectedid=None, parent=None, title="Select Evolution Folder"):
0171     # we do the imports etc in the function so that this file won't
0172     # require gui code unless this function is called
0173 
0174     import wx
0175     import wx.gizmos
0176 
0177     dlg=wx.Dialog(parent, -1, title, style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER, size=(450,350))
0178     vbs=wx.BoxSizer(wx.VERTICAL)
0179     
0180     tl=wx.gizmos.TreeListCtrl(dlg, -1, style=wx.TR_DEFAULT_STYLE|wx.TR_HIDE_ROOT)
0181 
0182     tl.AddColumn("Name")
0183     tl.SetMainColumn(0)
0184     tl.AddColumn("Type")
0185     tl.SetColumnWidth(0, 300)
0186 
0187 
0188 
0189     def addnode(parent, item, selected):
0190         node=tl.AppendItem(parent, item['name'])
0191         if selected==item['folderid']:
0192             tl.SelectItem(node)
0193         tl.SetItemText(node, ", ".join(item['type']), 1)
0194         tl.SetPyData(node, item)
0195         if item.has_key("children"):
0196             for child in item['children']:
0197                 addnode(node, child, selected)
0198             tl.Expand(node)
0199 
0200     root=tl.AddRoot("?")
0201     tl.SetPyData(root, None)
0202 
0203     for f in getfolders():
0204         addnode(root, f, selectedid)
0205 
0206     # select first folder if nothing is selected
0207     if tl.GetPyData(tl.GetSelection()) is None:
0208         child,_=tl.GetFirstChild(root, 1234)
0209         tl.SelectItem(child)
0210 
0211     vbs.Add(tl, 1, wx.EXPAND|wx.ALL, 5)
0212 
0213     vbs.Add(wx.StaticLine(dlg, -1, style=wx.LI_HORIZONTAL), 0, wx.EXPAND|wx.ALL, 5)
0214 
0215     vbs.Add(dlg.CreateButtonSizer(wx.OK|wx.CANCEL|wx.HELP), 0, wx.ALIGN_CENTRE|wx.ALL, 5)
0216 
0217     dlg.SetSizer(vbs)
0218     dlg.SetAutoLayout(True)
0219 
0220     if dlg.ShowModal()==wx.ID_OK:
0221         folderid=tl.GetPyData(tl.GetSelection())['folderid']
0222     else:
0223         folderid=None
0224     dlg.Destroy()
0225     return folderid
0226 
0227 # we use a pathname like "~/evolution/local/Contacts" as the folder id
0228 # and the same as a "folder"
0229 
0230 class Match:
0231     def __init__(self, folder):
0232         self.folder=folder
0233 
0234 def getfolderfromid(id, default=False):
0235     "Return a folder object given the id"
0236 
0237     f=_findfolder(id)
0238     if f is not None:
0239         return f['folderid']
0240     
0241     if default:
0242         # look for a default
0243         for f in getfolders():
0244             if "address book" in f['type']:
0245                 return f["folderid"]
0246         # brute force
0247         return getfolders()[0]['folderid']
0248 
0249     return None
0250 
0251 def __findfolder(node, id):
0252     "Recursive function to locate a folder, using Match exception to return the found folder"
0253     if node['folderid']==id:
0254         raise Match(node)
0255     for c in node.get("children", []):
0256         __findfolder(c, id)
0257 
0258 def _findfolder(id):
0259     for f in getfolders():
0260         try:
0261             __findfolder(f, id)
0262         except Match,m:
0263             return m.folder # we found it
0264     return None
0265         
0266         
0267 def getfoldername(id):
0268     f=_findfolder(id)
0269     if f is None:
0270         raise AttributeError("No such folder "+id)
0271 
0272     n=[]
0273     while f:
0274         n=[f['name']]+n
0275         f=f.get('parent',None)
0276 
0277     return " / ".join(n)
0278 
0279 def getfolderid(folder):
0280     return folder
0281     
0282 
0283 if __name__=="__main__":
0284     # a folder selector
0285     import wx
0286     import wx.gizmos
0287 
0288     app=wx.PySimpleApp()
0289     
0290     folder=pickfolder()
0291     print folder
0292 
0293     print "\n".join(getcontacts(folder))
0294                      
0295             
0296 

Generated by PyXR 0.9.4