| Trees | Indices | Help |
|
|---|
|
|
1 ### BITPIM
2 ###
3 ### Copyright (C) 2003 Roger Binns <rogerb@rogerbinns.com>
4 ### Copyright (C) 2003 Scott Craig <scott.craig@shaw.ca>
5 ### Copyright (C) 2003 Alan Gonzalez <agonzalez@yahoo.com>
6 ###
7 ### This program is free software; you can redistribute it and/or modify
8 ### it under the terms of the BitPim license as detailed in the LICENSE file.
9 ###
10 ### $Id: com_lgtm520.py 4543 2008-01-04 23:44:24Z djpham $
11
12 "Talk to the LG TM520/VX10 cell phone"
13
14 # standard modules
15 import re
16 import time
17 import cStringIO
18 import sha
19
20 # my modules
21 import common
22 import p_lgtm520
23 import com_brew
24 import com_phone
25 import com_lg
26 import prototypes
27
28 typemap = {
29 'fwd': ( 'home', 'office', 'cell', 'pager', 'data' ),
30 'rev': { 'home': 0, 'office': 1, 'cell': 2, 'pager': 3, 'data': 4, 'fax': 4 }
31 }
32
34 "Talk to the LG TM520/VX10 cell phone"
35 desc="LG-TM520/VX10"
36 protocolclass=p_lgtm520
37 serialsname='lgtm520'
38
39 ringtonelocations=(
40 # offset, index file, files location, type, maximumentries
41 # There really isn't an index file
42 ( 0, "", "ringer", "ringers", 5),
43 # phonebook linkage
44 #( 0, "pim/midiringer.dat", "ringer", "ringers", 199),
45 # main ringer
46 #( 0, "pim/ringtitle.dat", "ringer", "ringers", 1),
47 )
48
49 builtinringtones=( 'Standard1', 'Standard2', 'Standard3', 'Standard4',
50 'Standard5', 'Radetzky March', 'Nocturn', 'Carmen',
51 'La Traviata', 'Liberty Bell', 'Semper Fidelis',
52 'Take Me Out', 'Turkey In The Straw', 'We Wish...',
53 'Csikos Post', 'Bumble Bee Twist', 'Badinerie',
54 'Silken Ladder', 'Chestnut' )
55
56 # phone uses Jan 1, 1980 as epoch. Python uses Jan 1, 1970. This is difference
57 # plus a fudge factor of 5 days, 20 hours for no reason I can find
58 _tm520epochtounix=315532800+460800
59 _brewepochtounix=315532800+460800 # trying to override inherited entry
60 _calrepeatvalues={ 0: None, 1: None, 2: 'daily' }
61
62 getwallpapers=None
63
65 com_phone.Phone.__init__(self, logtarget, commport)
66 com_brew.BrewProtocol.__init__(self)
67 com_lg.LGPhonebook.__init__(self)
68 self.log("Attempting to contact phone")
69 self.mode=self.MODENONE
70
72 """Gets information fundamental to interopating with the phone and UI."""
73
74 # use a hash of ESN and other stuff (being paranoid)
75 self.log("Retrieving fundamental phone information")
76 self.log("Phone serial number")
77 results['uniqueserial']=sha.new(self.getfilecontents("nvm/$SYS.ESN")).hexdigest()
78 results['ringtone-index'] = {}
79 ringcount = 1
80 for r in self.builtinringtones:
81 results['ringtone-index'][ringcount] = {'name': r, 'origin': 'builtin'}
82 ringcount += 1
83
84 try:
85 ringers=self.getfilesystem('ringer')
86 for r in ringers.keys():
87 results['ringtone-index'][ringcount] = {'name': r[len('ringer/'):], 'origin': 'ringers'}
88 ringcount +=1
89 except com_brew.BrewNoSuchDirectoryException:
90 self.log("Ringer directory doesn't exist, firmware might not be download capable")
91 self.log("Fundamentals retrieved")
92 return results
93
95 self.mode = self.MODEPHONEBOOK
96 self.sendpbcommand(self.protocolclass.pbstartsyncrequest(), self.protocolclass.pbstartsyncresponse)
97
98 self.mode = self.MODEBREW
99 res=self.sendpbcommand(self.protocolclass.pbinforequest(), self.protocolclass.pbinforesponse)
100
101 return res.numentries
102
104 req=self.protocolclass.pbendsyncrequest()
105 self.sendpbcommand(req, self.protocolclass.pbendsyncresponse)
106
108 index={}
109 try:
110 buf=prototypes.buffer(self.getfilecontents("pim/midiringer.dat"))
111 except com_brew.BrewNoSuchFileException:
112 # file may not exist
113 result['intermediate'] = index
114 return
115 g=self.protocolclass.ringindex()
116 g.readfrombuffer(buf, logtitle="ringer index")
117 for i in g.items:
118 if i.name != "default":
119 index[i.index+1]=i.name[len('ringer/'):]
120 result['intermediate'] = index
121
123 #cats=[]
124 #for i in result['groups']:
125 # if result['groups'][i]['name']!='No Group':
126 # cats.append(result['groups'][i]['name'])
127 #result['categories'] = cats
128 del result['intermediate']
129
131 """Reads the phonebook data. The L{getfundamentals} information will
132 already be in result."""
133 pbook={}
134 # Bug in the phone. if you repeatedly read the phone book it starts
135 # returning a random number as the number of entries. We get around
136 # this by switching into brew mode which clears that.
137 #self.mode=self.MODENONE
138 #self.setmode(self.MODEBREW)
139
140 self.pregetpb(result)
141 self.log("Reading number of phonebook entries")
142
143 numentries=self.pbinit()
144 self.log("There are %d entries" % (numentries,))
145 for i in range(0, numentries):
146 ### Read current entry
147 req=self.protocolclass.pbreadentryrequest()
148 res=self.sendpbcommand(req, self.protocolclass.pbreadentryresponse)
149 self.log("Read entry "+`i`+" - "+res.entry.name)
150 entry=self.extractphonebookentry(res.entry, result)
151 pbook[i]=entry
152 self.progress(i, numentries, res.entry.name)
153 #### Advance to next entry
154 req=self.protocolclass.pbnextentryrequest()
155 self.sendpbcommand(req, self.protocolclass.pbnextentryresponse)
156
157 self.progress(numentries, numentries, "Phone book read completed")
158 result['phonebook']=pbook
159 self.pbend()
160 self.postgetdb(result)
161
162 print "returning keys",result.keys()
163 return pbook
164
166 """Return a phonebook entry in BitPim format"""
167 res={}
168 # serials
169 res['serials']=[ {'sourcetype': self.serialsname, 'serial1': entry.serial1, 'serial2': entry.serial2,
170 'sourceuniqueid': fundamentals['uniqueserial']} ]
171 # only one name
172 res['names']=[ {'full': entry.name} ]
173 # only one email
174 if len(entry.email) > 0: res['emails']=[ {'email': entry.email} ]
175 res['flags'] = []
176 # we only supply secret/voicetag if it is true
177 if entry.secret: res['flags'].append({'secret': entry.secret })
178 if entry.voicetag: res['flags'].append({'voicetag': entry.voicetag })
179
180 # ringtones
181 res['ringtones'] = []
182 if entry.ringtone > 0:
183 # individual ringtone
184 if entry.ringtone > len(self.builtinringtones):
185 # custom individual ringtone
186 try:
187 res['ringtones'].append({'ringtone': fundamentals['intermediate'][entry.entrynumber], 'use': 'call'})
188 except:
189 self.log('Custom ringtone not properly assigned for '+entry.name)
190 else:
191 # stock individual ringtone
192 res['ringtones'].append({'ringtone': fundamentals['ringtone-index'][entry.ringtone]['name'], 'use': 'call'})
193 # 5 phone numbers
194 res['numbers']=[]
195 numbernumber=0
196 for type in typemap['fwd']:
197 item = entry.numbers[numbernumber]
198 if len(item.number) > 0:
199 res['numbers'].append({'number': item.number, 'type': type, 'sum': item.chksum })
200 if numbernumber == entry.default: res['numbers'][-1]['speeddial'] = entry.entrynumber
201 numbernumber+=1
202 return res
203
205
207 self.savegroups(data)
208 # To write the phone book, we scan through all existing entries
209 # and record their record number and serials.
210 # We then delete any entries that aren't in data
211 # We then write out our records, usng overwrite or append
212 # commands as necessary
213 serialupdates=[]
214 existingpbook={} # keep track of the phonebook that is on the phone
215 ringindex = {}
216
217 # Ringtones
218 pbook = data['phonebook']
219 for i in pbook.keys():
220 if not 'ringtone' in pbook[i]: continue
221 if not pbook[i]['ringtone'] is None and not pbook[i]['ringtone'] in self.builtinringtones:
222 ringindex[pbook[i]['entrynumber']] = pbook[i]['ringtone'];
223
224 ringidxfile=self.protocolclass.ringindex()
225 for i in range(1, 200):
226 ringerentry = self.protocolclass.ringentry()
227 ringerentry.index = i
228 ringerentry.name = ''
229 if i in ringindex: ringerentry.name = 'ringer/'+ringindex[i]
230 ringidxfile.items.append(ringerentry)
231 buf=prototypes.buffer()
232 ringidxfile.writetobuffer(buf, logtitle="Custom ringer index")
233
234 # ::TODO:: this code looks wrong. Why isn't the if before the loop above?
235 # and if the user deleted all ringers then shouldn't the file be truncated
236 # rather than ignored?
237 if len(ringindex) > 0:
238 self.writefile("pim/midiringer.dat", buf.getvalue())
239
240 #self.mode=self.MODENONE
241 #self.setmode(self.MODEBREW) # see note in getphonebook() for why this is necessary
242 #self.setmode(self.MODEPHONEBOOK)
243 # similar loop to reading
244 numexistingentries=self.pbinit()
245 progressmax=numexistingentries+len(data['phonebook'].keys())
246 progresscur=0
247 self.log("There are %d existing entries" % (numexistingentries,))
248 for i in range(0, numexistingentries):
249 ### Read current entry
250 req=self.protocolclass.pbreadentryrequest()
251 res=self.sendpbcommand(req, self.protocolclass.pbreadentryresponse)
252
253 entry={ 'number': res.entry.entrynumber, 'serial1': res.entry.serial1,
254 'serial2': res.entry.serial2, 'name': res.entry.name}
255 assert entry['serial1']==entry['serial2'] # always the same
256 self.log("Reading entry "+`i`+" - "+entry['name'])
257 existingpbook[i]=entry
258 self.progress(progresscur, progressmax, "existing "+entry['name'])
259 #### Advance to next entry
260 req=self.protocolclass.pbnextentryrequest()
261 self.sendpbcommand(req, self.protocolclass.pbnextentryresponse)
262 progresscur+=1
263 # we have now looped around back to begining
264
265 # Find entries that have been deleted
266 pbook=data['phonebook']
267 dellist=[]
268 for i in range(0, numexistingentries):
269 ii=existingpbook[i]
270 serial=ii['serial1']
271 item=self._findserial(serial, pbook)
272 if item is None:
273 dellist.append(i)
274
275 progressmax+=len(dellist) # more work to do
276
277 # Delete those entries
278 for i in dellist:
279 progresscur+=1
280 numexistingentries-=1 # keep count right
281 ii=existingpbook[i]
282 self.log("Deleting entry "+`i`+" - "+ii['name'])
283 req=self.protocolclass.pbdeleteentryrequest()
284 req.serial1=ii['serial1']
285 req.serial2=ii['serial2']
286 req.entrynumber=ii['number']
287 self.sendpbcommand(req, self.protocolclass.pbdeleteentryresponse)
288 self.progress(progresscur, progressmax, "Deleting "+ii['name'])
289 # also remove them from existingpbook
290 del existingpbook[i]
291
292 # counter to keep track of record number (otherwise appends don't work)
293 counter=0
294 # Now rewrite out existing entries
295 keys=existingpbook.keys()
296 existingserials=[]
297 keys.sort() # do in same order as existingpbook
298 for i in keys:
299 progresscur+=1
300 ii=pbook[self._findserial(existingpbook[i]['serial1'], pbook)]
301 self.log("Rewriting entry "+`i`+" - "+ii['name'])
302 self.progress(progresscur, progressmax, "Rewriting "+ii['name'])
303 entry=self.makeentry(counter, ii, data)
304 counter+=1
305 existingserials.append(existingpbook[i]['serial1'])
306 req=self.protocolclass.pbupdateentryrequest()
307 req.entry=entry
308 res=self.sendpbcommand(req, self.protocolclass.pbupdateentryresponse)
309 serialupdates.append( ( ii["bitpimserial"],
310 {'sourcetype': self.serialsname, 'serial1': res.serial1, 'serial2': res.serial1,
311 'sourceuniqueid': data['uniqueserial']})
312 )
313 assert ii['serial1']==res.serial1 # serial should stay the same
314
315 # Finally write out new entries
316 keys=pbook.keys()
317 keys.sort()
318 for i in keys:
319 ii=pbook[i]
320 if ii['serial1'] in existingserials:
321 continue # already wrote this one out
322 progresscur+=1
323 entry=self.makeentry(counter, ii, data)
324 counter+=1
325 self.log("Appending entry "+ii['name'])
326 self.progress(progresscur, progressmax, "Writing "+ii['name'])
327 req=self.protocolclass.pbappendentryrequest()
328 req.entry=entry
329 res=self.sendpbcommand(req, self.protocolclass.pbappendentryresponse)
330 serialupdates.append( ( ii["bitpimserial"],
331 {'sourcetype': self.serialsname, 'serial1': res.newserial, 'serial2': res.newserial,
332 'sourceuniqueid': data['uniqueserial']})
333 )
334
335 self.pbend()
336 data["serialupdates"]=serialupdates
337 if len(ringindex) == 0: return
338
339
341 """Searches dict to find entry with matching serial. If not found,
342 returns None"""
343 for i in dict:
344 if dict[i]['serial1']==serial:
345 return i
346 return None
347
349 res={}
350 # Now read schedule
351 buf=prototypes.buffer(self.getfilecontents("sch/sch_00.dat"))
352 sc=self.protocolclass.schedulefile()
353 sc.readfrombuffer(buf, logtitle="Calendar")
354 self.logdata("Calendar", buf.getdata(), sc)
355 for event in sc.events:
356 entry={}
357 if event.state == 0 or event.repeat == 0: continue # deleted entry
358 if event.date == 0x11223344: continue # blanked entry
359 date = event.date
360 date += self._tm520epochtounix
361 entry['start'] = self.decodedate(date)
362 entry['end'] = self.decodedate(date)
363 entry['pos']=event.pos
364 entry['description'] = event.description
365 if event.pos == 0: entry['description'] = 'Wake Up'
366 entry['alarm'] = 0
367 if event.alarm & 0xB0 == 0xB0: entry['alarm'] = 1
368 entry['ringtone'] = 0
369 entry['changeserial'] = 0
370 entry['repeat'] = self._calrepeatvalues[event.repeat]
371
372 # Hack - using snoozedelay to store the DST flag
373 entry['snoozedelay'] = time.localtime(date)[8]
374 res[event.pos]=entry
375
376 result['calendar']=res
377 return result
378
380 # ::TODO:: obey merge param
381 # what will be written to the files
382 eventsf=self.protocolclass.schedulefile()
383
384 # what are we working with
385 cal=dict['calendar']
386 newcal={}
387 keys=cal.keys()
388 keys.sort()
389
390 # number of entries
391 numactiveitems=len(keys)
392 self.log("There are %d calendar entries" % (numactiveitems,))
393 counter = 1
394
395 # Write out alarm entry - see below for special handling
396 alarm=self.protocolclass.scheduleevent()
397 alarm.pos = 0
398 alarm.date = 0x11223344
399 alarm.state = 0
400 alarm.alarm = 0x80
401 alarm.repeat = 0
402 alarm.description = " NO ENTRY NO ENTRY "
403
404 eventsf.events.append(dummy)
405 # play with each entry
406 for k in keys:
407 # entry is what we will return to user
408 entry=cal[k]
409 data=self.protocolclass.scheduleevent()
410 if counter >= 50:
411 self.log("More than 49 entries in calendar, only writing out the first 49")
412 break
413 data.pos=counter
414 counter+=1
415 entry['pos']=data.pos
416 # simple copy of these fields
417 for field in 'start','description':
418 setattr(data,field,entry[field])
419
420 data.state = 2
421 data.alarm = 0
422 data.repeat = 1
423
424 dst = -1
425 if entry['snoozedelay']: dst = entry['snoozedelay']
426 data.date = self.encodedate(entry['start'],dst)-self._tm520epochtounix
427
428 # Special alarm handling - TM520 is not capable of having an alarmed
429 # entry, so we will only alarm on the earliest entry
430 if entry['alarm'] > 0 and ( alarm.date == 0x11223344 or data.date < alarm.date ):
431 alarm.date = data.date
432 alarm.state = 1
433 alarm.alarm = 0xB0
434 alarm.repeat = 1
435 if entry['repeat'] > 0: alarm.repeat = 2
436
437 # put entry in nice shiny new dict we are building
438 newcal[data.pos]=entry
439 eventsf.events.append(data)
440
441 if counter < 50:
442 for i in range(counter, 50):
443 dummy=self.protocolclass.scheduleevent()
444 dummy.pos = i
445 dummy.date = 0x11223344
446 dummy.state = 0
447 dummy.alarm = 0x80
448 dummy.repeat = 0
449 dummy.description = " NO ENTRY NO ENTRY "
450 eventsf.events.append(dummy)
451
452 # scribble everything out
453 buf=prototypes.buffer()
454 eventsf.writetobuffer(buf, logtitle="Writing calendar")
455 self.writefile("sch/sch_00.dat", buf.getvalue())
456
457 # fix passed in dict
458 dict['calendar']=newcal
459
460 return dict
461
463 media = {}
464 try:
465 ringers=self.getfilesystem('ringer')
466 for r in ringers:
467 name = r[len('ringer/'):]
468 media[name] = self.getfilecontents(r)
469 except com_brew.BrewNoSuchDirectoryException: pass
470 result['ringtone'] = media
471 return result
472
474 try:
475 ringers=self.getfilesystem('ringer')
476 for r in ringers.keys():
477 ringers[r[len('ringer/'):]] = ringers[r]
478 del ringers[r]
479 except com_brew.BrewNoSuchDirectoryException:
480 self.log("Ringer directory doesn't exist, firmware might not be download capable")
481 return results
482
483 index={}
484 for r in results['ringtone-index']:
485 if r['origin'] == 'ringers': index.append(r['name'])
486
487 for r in ringers.keys():
488 # it is in the original index, are we writing it back out?
489 if r in index and ringers[r]['size'] == len(results['ringtone'][r]): continue
490 else:
491 if not merge or r in index:
492 # go ahead and delete unwanted files
493 print "deleting",r
494 self.rmfile("ringer/"+r)
495 if r in index: self.writefile("ringer/"+r, results['ringtone'][r])
496
498 """Unpack 32 bit value into date/time
499
500 @rtype: tuple
501 @return: (year, month, day, hour, minute)
502 """
503 return time.localtime(val)[:5]
504
510
512 if name is None: return 0
513 for i in index:
514 if index[i]['name'] == name:
515 return i
516 self.log("%s: Unable to find ringtone %s in the index. Setting to default." % (pbentryname, name))
517 return 0
518
520 sumtbl = { '0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9,
521 'H': 24, 'T': 36, '#': -13, '*': -6 }
522 entry.number = number
523 entry.chksum = 0
524 lastseven = number
525 if len(lastseven) > 7: lastseven = lastseven[len(lastseven)-7:]
526 if len(lastseven) > 0:
527 for i in lastseven:
528 entry.chksum += sumtbl[i]
529 else: entry.chksum = 255
530
532 e=self.protocolclass.pbentry()
533 e.entrynumber=counter
534
535 for k in entry:
536 # special treatment for lists
537 if k == 'numbers':
538 l=getattr(e,k)
539 for item in entry[k]:
540 num=self.protocolclass.numentry()
541 self._calcnumsum(num, item)
542 l.append(num)
543 elif k == 'ringtone':
544 e.ringtone = self._findringtoneindex(dict['ringtone-index'], entry[k], entry['name'])
545 else:
546 # everything else we just set
547 setattr(e,k,entry[k])
548
549 return e
550
552 """Convert the phone number into something the phone understands
553
554 All digits, P, H, T, * and # are kept, everything else is removed"""
555 return re.sub("[^0-9HPT#*]", "", str)
556
557
559 serialsname='lgtm520'
560 WALLPAPER_WIDTH=100
561 WALLPAPER_HEIGHT=100
562 MAX_WALLPAPER_BASENAME_LENGTH=19
563 MAX_RINGTONE_BASENAME_LENGTH=19
564 WALLPAPER_FILENAME_CHARS="abcdefghijklmnopqrstuvwxyz0123456789 ."
565 RINGTONE_FILENAME_CHARS="abcdefghijklmnopqrstuvwxyz0123456789 ."
566 WALLPAPER_CONVERT_FORMAT="bmp"
567
568 usbids = ( )
569 deviceclasses = ("serial", )
570 _supportedsyncs=(
571 ('phonebook', 'read', None), # all phonebook reading
572 ('calendar', 'read', None), # all calendar reading
573 ('ringtone', 'read', None), # all ringtone reading
574 ('phonebook', 'write', 'OVERWRITE'), # only overwriting phonebook
575 ('calendar', 'write', 'OVERWRITE'), # only overwriting calendar
576 ('ringtone', 'write', 'MERGE'),
577 ('ringtone', 'write', 'OVERWRITE'),
578 )
579
581 "Converts the data to what will be used by the phone"
582
583 results = {}
584 speeddial = {}
585 entrynumber = 1
586
587 for pbentry in data['phonebook']:
588 e = {} # entry out
589 entry = data['phonebook'][pbentry] # entry in
590
591 try:
592 try:
593 e['name'] = helper.getfullname(entry.get('names', []),1,1,16)[0]
594 except IndexError: raise helper.ConversionFailed("No name assigned to entry")
595 try:
596 e['email']= helper.getemails(entry.get('emails', []),0,1,48)[0]
597 except IndexError: e['email'] = ""
598 e['ringtone'] = helper.getringtone(entry.get('ringtones', []), 'call', None)
599 e['secret'] = helper.getflag(entry.get('flags',[]), 'secret', False)
600 e['voicetag'] = helper.getflag(entry.get('flags',[]), 'voicetag', False)
601 e['default'] = 0
602
603 numbers = entry.get('numbers', [])
604 if len(numbers) < 1: raise helper.ConversionFailed("Too few numbers. Need at least 1 number")
605 e['numbers']=[ '', '', '', '', '' ]
606 available = 5
607 deferred = []
608 for num in numbers:
609 number=phonize(num['number'])
610 if len(number) == 0: continue # no actual digits in number
611 if len(number) > 32: number = number[:32] # truncate long number
612 if available == 0: break # all slots filled
613 if not 'type' in num:
614 deferred.append(num)
615 continue
616 elif not num['type'] in typemap['rev']:
617 deferred.append(num)
618 continue
619 else:
620 typeidx = typemap['rev'][num['type']]
621 if len(e['numbers'][typeidx]) == 0:
622 e['numbers'][typeidx] = number
623 if 'speeddial' in num and not 'entrynumber' in e:
624 if not num['speeddial'] in speeddial:
625 e['entrynumber'] = num['speeddial']
626 e['default'] = typeidx
627 available -= 1
628 if available > 0 and len(deferred) > 0:
629 for num in deferred:
630 if available == 0: break
631 number=phonize(num['number'])
632 if len(number) > 32: number = number[:32] # truncate long number
633 for slot in range(0, 5):
634 if len(e['numbers'][slot]) > 0: continue
635 e['numbers'][slot] = number
636 if 'speeddial' in num and not 'entrynumber' in e:
637 if not num['speeddial'] in speeddial:
638 e['entrynumber'] = num['speeddial']
639 e['default'] = slot
640 available -= 1
641 if available == 5:
642 raise helper.ConversionFailed("The phone numbers didn't have any digits for this entry")
643 if not 'entrynumber' in e:
644 while entrynumber in speedial:
645 entrynumber += 1
646 if entrynumber > 199:
647 self.log("Too many entries in phonebook, only 199 entries supported")
648 break
649 e['entrynumber'] = entrynumber
650 speeddial[e['entrynumber']] = pbentry
651
652 serial1 = helper.getserial(entry.get('serials', []), self.serialsname, data['uniqueserial'], 'serial1', 0)
653 serial2 = helper.getserial(entry.get('serials', []), self.serialsname, data['uniqueserial'], 'serial2', serial1)
654
655 e['serial1'] = serial1
656 e['serial2'] = serial2
657 for ss in entry["serials"]:
658 if ss["sourcetype"]=="bitpim":
659 e['bitpimserial']=ss
660 assert e['bitpimserial']
661
662 results[pbentry] = e
663
664 except helper.ConversionFailed: continue
665
666 data['phonebook'] = results
667 return data
668
| Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Sun Jan 24 16:24:59 2010 | http://epydoc.sourceforge.net |