Commit 2517cfdb authored by Sriram Karra's avatar Sriram Karra

"Folder" support for BBDB

BBDB does not support a built in folder system. It is all one flat
collection of contacts in the .bbdb. Here we try to logically
arrange the contacts collection into folders based on a notes field.

By default this notes field should have key named 'folder'. The value
of this field is taken as the name of the folder. The key name can
be changed by editing config.json in the appropriate place.

Note that the point of logically grouping the contacts into Folders
is to enable sync of only a subset of your BBDB contacts with an
external source. This is particularly helpful if you have a very large
collection of contacts, but are interested in only synching those with
phone numbers or from yoru company, or any other criterion you can think
of. Just add the folder note to those contacts, and use that folder name
in a new sync profile.
parent 997845a5
// -*- javascript -*-
// Last Modified : Wed Apr 25 18:13:02 IST 2012
// Last Modified : Sun Apr 29 12:39:46 IST 2012
//
// Copyright (C) 2011, 2012 Sriram Karra <karra.etc@gmail.com>
//
......@@ -179,6 +179,7 @@
'gender' : 'gender',
'created' : 'creation-date',
'updated' : 'timestamp',
'folder' : 'folder',
},
// Phone numbers are stored as an array of vectors in BBDB
......
##
## Created : Sat Apr 07 20:03:04 IST 2012
## Last Modified : Wed Apr 18 06:05:42 IST 2012
## Last Modified : Sun Apr 29 12:30:17 IST 2012
##
## Copyright (C) 2012 Sriram Karra <karra.etc@gmail.com>
##
......@@ -31,7 +31,7 @@ def main (argv=None):
tests = TestBBContact(config_fn='../config.json',
state_fn='../state.json',
bbfn=bbfn)
tests.print_contacts(cnt=1)
tests.print_contacts(cnt=0)
# tests.write_to_file()
class TestBBContact:
......@@ -40,7 +40,8 @@ class TestBBContact:
self.config = Config(config_fn, state_fn)
self.bb = BBPIMDB(self.config, bbfn)
self.deff = self.bb.get_def_folder()
ms = self.bb.get_def_msgstore()
self.deff = ms.get_folder(ms.get_def_folder_name())
def print_contacts (self, cnt):
self.deff.print_contacts(cnt=cnt)
......
##
## Created : Fri Apr 06 19:08:32 IST 2012
## Last Modified : Fri Apr 27 20:26:25 IST 2012
## Last Modified : Sun Apr 29 12:17:41 IST 2012
##
## Copyright (C) 2012 Sriram Karra <karra.etc@gmail.com>
##
......@@ -84,6 +84,12 @@ class BBContact(Contact):
## Now onto the non-abstract methods.
##
def get_bbdb_folder (self):
return self._get_prop('bbdb_folder')
def set_bbdb_folder (self, bbdb_folder):
return self._set_prop('bbdb_folder', bbdb_folder)
def get_rec (self):
return self._get_att('rec')
......@@ -91,7 +97,7 @@ class BBContact(Contact):
return self._set_att('rec', rec)
def init_props_from_rec (self, rec):
con_re = self.get_db().get_con_re()
con_re = self.get_store().get_con_re()
parse_res = re.search(con_re, rec)
if not parse_res:
......@@ -142,7 +148,7 @@ class BBContact(Contact):
def _snarf_aka_from_parse_res (self, pr):
aka = pr['aka']
if aka and aka != 'nil':
str_re = self.get_db().get_str_re()
str_re = self.get_store().get_str_re()
aka = re.findall(str_re, aka)
nick = aka[0]
rest = aka[1:]
......@@ -162,7 +168,7 @@ class BBContact(Contact):
if cs and cs != 'nil':
## The first company goes into the Company field, the rest we will
## push into the custom field (as aa json encoded string)
str_re = self.get_db().get_str_re()
str_re = self.get_store().get_str_re()
cs = re.findall(str_re, cs)
self.set_company(chompq(cs[0]))
self.add_custom('company', demjson.encode(cs[1:]))
......@@ -171,7 +177,7 @@ class BBContact(Contact):
ems = pr['emails']
if ems:
str_re = self.get_db().get_str_re()
str_re = self.get_store().get_str_re()
ems = re.findall(str_re, ems)
ems = [chompq(x) for x in ems]
......@@ -212,8 +218,8 @@ class BBContact(Contact):
return (res['home'], res['work'], res['other'])
def _snarf_postal_from_parse_res (self, pr):
adr_re = self.get_db().get_adr_re()
str_re = self.get_db().get_str_re()
adr_re = self.get_store().get_adr_re()
str_re = self.get_store().get_str_re()
addrs = re.findall(adr_re, pr['addrs'])
for addr in addrs:
......@@ -254,7 +260,7 @@ class BBContact(Contact):
add)
def _snarf_phones_from_parse_res (self, pr):
ph_re = self.get_db().get_ph_re()
ph_re = self.get_store().get_ph_re()
phs = re.findall(ph_re, pr['phones']) if pr['phones'] else None
if phs:
......@@ -307,11 +313,13 @@ class BBContact(Contact):
logging.error('Error in Config file. No notes_map field for bb')
return
stag_re = self.get_db().get_sync_tag_re()
note_re = self.get_db().get_note_re()
stag_re = self.get_store().get_sync_tag_re()
note_re = self.get_store().get_note_re()
notes = re.findall(note_re, pr['notes'])
custom = {}
self.set_bbdb_folder(None)
# logging.debug('bb:snfpr:stag_re: %s', stag_re)
# keys = [note[0] for note in notes]
# logging.debug('bb:snfpr:Keys: %s', keys)
......@@ -354,6 +362,8 @@ class BBContact(Contact):
self.add_web_work(val)
elif re.search(noted['middle_name'], key):
self.add_middlename(val)
elif re.search(noted['folder'], key):
self.set_bbdb_folder(val)
else:
## The rest of the stuff go into the 'Custom' field...
custom.update({key : val})
......
##
## Created : Tue Mar 13 14:26:01 IST 2012
## Last Modified : Wed Apr 18 16:48:51 IST 2012
## Last Modified : Sun Apr 29 12:11:46 IST 2012
##
## Copyright (C) 2012 Sriram Karra <karra.etc@gmail.com>
##
......@@ -35,7 +35,7 @@ class Folder:
UNKNOWN_t : 'other',
}
def __init__ (self, db):
def __init__ (self, db, store=None):
# Folders have properties that need to persist in the underlying
# database. We call them 'props'. These are defined and tracked in a
# single dictionary. Each of the derived classes will, of course, add
......@@ -54,6 +54,7 @@ class Folder:
# like any other object attributes
self.set_db(db)
self.set_store(store)
self.set_config(db.get_config())
@abstractmethod
......@@ -207,6 +208,12 @@ class Folder:
def set_db (self, db):
self.db = db
def get_store (self):
return self.store
def set_store (self, store):
self.store = store
def get_type (self):
return self._get_prop('type')
......
##
## Created : Sat Apr 07 20:03:04 IST 2012
## Last Modified : Fri Apr 27 17:00:18 IST 2012
## Last Modified : Sun Apr 29 12:26:43 IST 2012
##
## Copyright (C) 2012 Sriram Karra <karra.etc@gmail.com>
##
......@@ -18,15 +18,17 @@ class BBDBFileFormatError(Exception):
class BBContactsFolder(Folder):
default_folder_id = 'default'
def __init__ (self, db, fn):
Folder.__init__(self, db)
def __init__ (self, db, fn, store=None):
logging.debug('New BBContactsFolder: %s', fn)
Folder.__init__(self, db, store)
self.set_clean()
self.set_type(Folder.CONTACT_t)
self.set_itemid(fn)
self.set_name(fn)
self.contacts = {}
self.parse_file()
def __del__ (self):
if self.is_dirty():
......@@ -201,78 +203,20 @@ class BBContactsFolder(Folder):
def add_contact (self, bbc):
self.contacts.update({bbc.get_itemid() : bbc})
def get_contacts (self):
return self.contacts
def set_file_format (self, ver):
return self._set_prop('file_format', ver)
def get_file_format (self):
return self._get_prop('file_format')
def parse_file (self, fn=None):
if not fn:
fn = self.get_name()
logging.info('Parsing BBDB file %s...', fn)
with codecs.open(fn, encoding='utf-8') as bbf:
ff = bbf.readline()
if re.search('coding:', ff):
# Ignore first line if it is: ;; -*-coding: utf-8-emacs;-*-
ff = bbf.readline()
# Processing: ;;; file-format: 8
res = re.search(';;; file-(format|version):\s*(\d+)', ff)
if not res:
bbf.close()
raise BBDBFileFormatError('Unrecognizable format line: %s' % ff)
ver = int(res.group(2))
self.set_file_format(ver)
if ver < 7:
bbf.close()
raise BBDBFileFormatError(('Need minimum file format ver 7. ' +
'. File version is: %d' ) % ver)
cnt = 0
while True:
ff = bbf.readline()
if ff == '':
break
if re.search('^;', ff):
continue
c = BBContact(self, rec=ff.rstrip())
self.add_contact(c)
cnt += 1
logging.info('Successfully parsed %d entries.', cnt)
bbf.close()
def del_contact (self, bbc):
"""Remove the specified from the contact from this folder, and return
True. If it does not exist in the folder, returns False."""
def save_file (self, fn=None):
if not fn:
fn = self.get_name() + '.out'
logging.info('Saving BBDB Folder %s to file: %s...',
self.get_name(), fn)
with codecs.open(fn, 'w', encoding='utf-8') as bbf:
bbf.write(';; -*-coding: utf-8-emacs;-*-\n')
bbf.write(';;; file-format: 7\n')
for bbdbid, bbc in self.get_contacts().iteritems():
con = bbc.init_rec_from_props()
bbf.write('%s\n' % unicode(con))
bbf.close()
self.set_clean()
try:
del self.contacts[bbc.get_itemid()]
return True
except KeyError, e:
logging.debug('Trying to delete non-existent contact (%s) from '
'Folder: %s', bbc.get_itemid(), self.get_name())
return False
def get_user_fields_as_string (self):
# FIXME: Do something meanginful with this
return 'mail-alias'
def get_contacts (self):
return self.contacts
def print_contacts (self, cnt=0):
i = 0
......@@ -286,11 +230,3 @@ class BBContactsFolder(Folder):
logging.debug('Printed %d contacts from folder %s', i,
self.get_name())
##
## Some class methods
##
@classmethod
def get_default_folder_id (self):
return self.default_folder_id
##
## Created : Tue Mar 13 14:26:01 IST 2012
## Last Modified : Fri Apr 27 17:22:16 IST 2012
## Last Modified : Sun Apr 29 12:14:58 IST 2012
##
## Copyright (C) 2012 Sriram Karra <karra.etc@gmail.com>
##
......@@ -189,6 +189,9 @@ class Item:
def set_folder (self, val):
return self._set_att('folder', val)
def get_store (self):
return self.get_folder().get_store()
def get_db (self):
return self._get_att('db')
......
##
## Created : Tue Mar 13 14:26:01 IST 2012
## Last Modified : Wed Apr 11 18:36:40 IST 2012
## Last Modified : Sun Apr 29 12:27:36 IST 2012
##
## Copyright (C) 2012 Sriram Karra <karra.etc@gmail.com>
##
......@@ -45,7 +45,7 @@ class PIMDB:
self.folders = {'contacts':[],'tasks':[],'notes':[],'appts':[],}
self.sync_folders = {'contacts':[],'tasks':[],'notes':[],'appts':[],}
self.def_folder = {'contacts' : None, 'tasks' : None,
self.def_folder = {'contacts' : None, 'tasks' : None,
'notes' : None, 'appts' : None,}
## sync_lists are essentially, data structures used at the itme of
......@@ -68,14 +68,6 @@ class PIMDB:
raise NotImplementedError
@abstractmethod
def list_folders (self):
"""Print details of all folders in the PIMDB. Detail will typically
include one line per folder, with its name, and any identifier that
can be used for further referencing."""
raise NotImplementedError
@abstractmethod
def new_folder (self, fname, type):
"""Create a new folder of specified type and return an id. The folder
......@@ -193,6 +185,17 @@ class PIMDB:
return self._set_att('phones_map', None)
def list_folders (self):
"""Print details of all folders in the PIMDB. Detail will typically
include one line per folder, with its name, and any identifier that
can be used for further referencing."""
i = 1
for t in Folder.valid_types:
for f in self.get_folders(t):
logging.info(' %2d: %s', i, str(f))
i += 1
def get_folders (self, ftype=None):
"""Return all the folders of specified type. ftype should be one of
the valid folder types. If none is specifiedfor ftype, then the entire
......
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment