PyXR

c:\projects\bitpim\src \ phones \ com_lgvx4400.py



0001 ### BITPIM
0002 ###
0003 ### Copyright (C) 2003-2004 Roger Binns <rogerb@rogerbinns.com>
0004 ###
0005 ### This program is free software; you can redistribute it and/or modify
0006 ### it under the terms of the BitPim license as detailed in the LICENSE file.
0007 ###
0008 ### $Id: com_lgvx4400.py 4636 2008-07-21 03:25:38Z djpham $
0009 
0010 """Communicate with the LG VX4400 cell phone"""
0011 
0012 # standard modules
0013 import datetime
0014 import re
0015 import time
0016 import cStringIO
0017 import sha
0018 
0019 # my modules
0020 import bpcalendar
0021 import common
0022 import commport
0023 import copy
0024 import p_lgvx4400
0025 import p_brew
0026 import com_brew
0027 import com_phone
0028 import com_lg
0029 import helpids
0030 import prototypes
0031 import fileinfo
0032 import call_history
0033 import sms
0034 import memo
0035 
0036 class Phone(com_phone.Phone,com_brew.BrewProtocol,com_lg.LGPhonebook,com_lg.LGIndexedMedia):
0037     "Talk to the LG VX4400 cell phone"
0038 
0039     desc="LG-VX4400"
0040     helpid=helpids.ID_PHONE_LGVX4400
0041     wallpaperindexfilename="dloadindex/brewImageIndex.map"
0042     ringerindexfilename="dloadindex/brewRingerIndex.map"
0043     protocolclass=p_lgvx4400
0044     serialsname='lgvx4400'
0045 
0046     imagelocations=(
0047         # offset, index file, files location, type, maximumentries
0048         ( 10, "dloadindex/brewImageIndex.map", "brew/shared", "images", 30),
0049         )
0050 
0051     ringtonelocations=(
0052         # offset, index file, files location, type, maximumentries
0053         ( 50, "dloadindex/brewRingerIndex.map", "user/sound/ringer", "ringers", 30),
0054         )
0055 
0056     builtinimages=('Balloons', 'Soccer', 'Basketball', 'Bird',
0057                    'Sunflower', 'Puppy', 'Mountain House', 'Beach')
0058 
0059     builtinringtones=( 'Ring 1', 'Ring 2', 'Ring 3', 'Ring 4', 'Ring 5',
0060                        'Ring 6', 'Voices of Spring', 'Twinkle Twinkle',
0061                        'The Toreadors', 'Badinerie', 'The Spring', 'Liberty Bell',
0062                        'Trumpet Concerto', 'Eine Kleine', 'Silken Ladder', 'Nocturne',
0063                        'Csikos Post', 'Turkish March', 'Mozart Aria', 'La Traviata',
0064                        'Rag Time', 'Radetzky March', 'Can-Can', 'Sabre Dance', 'Magic Flute',
0065                        'Carmen' )
0066 
0067     
0068     def __init__(self, logtarget, commport):
0069         "Calls all the constructors and sets initial modes"
0070         com_phone.Phone.__init__(self, logtarget, commport)
0071         com_brew.BrewProtocol.__init__(self)
0072         com_lg.LGPhonebook.__init__(self)
0073         com_lg.LGIndexedMedia.__init__(self)
0074         self.log("Attempting to contact phone")
0075         self.mode=self.MODENONE
0076         self._cal_has_voice_id=hasattr(self.protocolclass, 'cal_has_voice_id') \
0077                                 and self.protocolclass.cal_has_voice_id
0078 
0079     def getfundamentals(self, results):
0080         """Gets information fundamental to interopating with the phone and UI.
0081 
0082         Currently this is:
0083 
0084           - 'uniqueserial'     a unique serial number representing the phone
0085           - 'groups'           the phonebook groups
0086           - 'wallpaper-index'  map index numbers to names
0087           - 'ringtone-index'   map index numbers to ringtone names
0088 
0089         This method is called before we read the phonebook data or before we
0090         write phonebook data.
0091         """
0092 
0093         # use a hash of ESN and other stuff (being paranoid)
0094         self.log("Retrieving fundamental phone information")
0095         self.log("Phone serial number")
0096         results['uniqueserial']=sha.new(self.get_esn()).hexdigest()
0097 
0098         self.getgroups(results)
0099         self.getwallpaperindices(results)
0100         self.getringtoneindices(results)
0101 
0102         self.log("Fundamentals retrieved")
0103 
0104         return results
0105 
0106     def savesms(self, result, merge):
0107         self._setquicktext(result)
0108         result['rebootphone']=True
0109         return result
0110 
0111     def _setquicktext(self, result):
0112         sf=self.protocolclass.sms_quick_text()
0113         quicktext=result.get('canned_msg', [])
0114         count=0
0115         for entry in quicktext:
0116             if count < self.protocolclass.SMS_CANNED_MAX_ITEMS:
0117                 sf.msgs.append(entry['text'][:self.protocolclass.SMS_CANNED_MAX_LENGTH-1])
0118                 count+=1
0119             else:
0120                 break
0121         if count!=0:
0122             # don't create the file if there are no entries 
0123             buf=prototypes.buffer()
0124             sf.writetobuffer(buf, logtitle="Writing calendar")
0125             self.writefile(self.protocolclass.SMS_CANNED_FILENAME, buf.getvalue())
0126         return
0127 
0128     def getsms(self, result):
0129         # get the quicktext (LG name for canned messages)
0130         result['canned_msg']=self._getquicktext()
0131         result['sms']=self._readsms()
0132         return result
0133 
0134     def _readsms(self):
0135         res={}
0136         # go through the sms directory looking for messages
0137         for item in self.listfiles("sms").values():
0138             folder=None
0139             for f,pat in self.protocolclass.SMS_PATTERNS.items():
0140                 if pat.match(item['name']):
0141                     folder=f
0142                     break
0143             if folder:
0144                 buf=prototypes.buffer(self.getfilecontents(item['name'], True))
0145                 self.logdata("SMS message file " +item['name'], buf.getdata())
0146             if folder=='Inbox':
0147                 sf=self.protocolclass.sms_in()
0148                 sf.readfrombuffer(buf, logtitle="SMS inbox item")
0149                 entry=self._getinboxmessage(sf)
0150                 res[entry.id]=entry
0151             elif folder=='Sent':
0152                 sf=self.protocolclass.sms_out()
0153                 sf.readfrombuffer(buf, logtitle="SMS sent item")
0154                 entry=self._getoutboxmessage(sf)
0155                 res[entry.id]=entry
0156             elif folder=='Saved':
0157                 sf=self.protocolclass.sms_saved()
0158                 sf.readfrombuffer(buf, logtitle="SMS saved item")
0159                 if sf.outboxmsg:
0160                     entry=self._getoutboxmessage(sf.outbox)
0161                 else:
0162                     entry=self._getinboxmessage(sf.inbox)
0163                 entry.folder=entry.Folder_Saved
0164                 res[entry.id]=entry
0165         return res 
0166 
0167     def _getquicktext(self):
0168         quicks=[]
0169         try:
0170             buf=prototypes.buffer(self.getfilecontents("sms/mediacan000.dat"))
0171             sf=self.protocolclass.sms_quick_text()
0172             sf.readfrombuffer(buf, logtitle="SMS quicktext file sms/mediacan000.dat")
0173             for rec in sf.msgs:
0174                 if rec.msg!="":
0175                     quicks.append({ 'text': rec.msg, 'type': sms.CannedMsgEntry.user_type })
0176         except com_brew.BrewNoSuchFileException:
0177             pass # do nothing if file doesn't exist
0178         return quicks
0179 
0180     def _getinboxmessage(self, sf):
0181         entry=sms.SMSEntry()
0182         entry.folder=entry.Folder_Inbox
0183         entry.datetime="%d%02d%02dT%02d%02d%02d" % (sf.GPStime)
0184         entry._from=self._getsender(sf.sender, sf.sender_length)
0185         entry.subject=sf.subject
0186         entry.locked=sf.locked
0187         if sf.priority==0:
0188             entry.priority=sms.SMSEntry.Priority_Normal
0189         else:
0190             entry.priority=sms.SMSEntry.Priority_High
0191         entry.read=sf.read
0192         txt=""
0193         if sf.num_msg_elements==1 and sf.bin_header1==0:
0194             txt=self._get_text_from_sms_msg_without_header(sf.msgs[0].msg, sf.msglengths[0].msglength)
0195         else:
0196             for i in range(sf.num_msg_elements):
0197                 txt+=self._get_text_from_sms_msg_with_header(sf.msgs[i].msg, sf.msglengths[i].msglength)
0198         entry.text=unicode(txt, errors='ignore')
0199         entry.callback=sf.callback
0200         return entry
0201 
0202     def _getoutboxmessage(self, sf):
0203         entry=sms.SMSEntry()
0204         entry.folder=entry.Folder_Sent
0205         entry.datetime="%d%02d%02dT%02d%02d00" % sf.timesent[:5]
0206         # add all the recipients
0207         for r in sf.recipients:
0208             if r.number:
0209                 confirmed=(r.status==5)
0210                 confirmed_date=None
0211                 if confirmed:
0212                     confirmed_date="%d%02d%02dT%02d%02d00" % r.timereceived
0213                 entry.add_recipient(r.number, confirmed, confirmed_date)
0214         entry.subject=sf.subject
0215         txt=""
0216         if sf.num_msg_elements==1 and not sf.messages[0].binary:
0217             txt=self._get_text_from_sms_msg_without_header(sf.messages[0].msg, sf.messages[0].length)
0218         else:
0219             for i in range(sf.num_msg_elements):
0220                 txt+=self._get_text_from_sms_msg_with_header(sf.messages[i].msg, sf.messages[i].length)
0221         entry.text=unicode(txt, errors='ignore')
0222         if sf.priority==0:
0223             entry.priority=sms.SMSEntry.Priority_Normal
0224         else:
0225             entry.priority=sms.SMSEntry.Priority_High
0226         entry.locked=sf.locked
0227         entry.callback=sf.callback
0228         return entry
0229 
0230     def _get_text_from_sms_msg_without_header(self, msg, num_septets):
0231         out=""
0232         for i in range(num_septets):
0233             tmp = (msg[(i*7)/8].byte<<8) | msg[((i*7)/8) + 1].byte
0234             bit_index = 9 - ((i*7) % 8)
0235             out += chr((tmp >> bit_index) & 0x7f)
0236         return out
0237 
0238     def _get_text_from_sms_msg_with_header(self, msg, num_septets):
0239         data_len = ((msg[0].byte+1)*8+6)/7
0240         seven_bits={}
0241         raw={}
0242         out={}
0243         # re-order the text into the correct order for separating into
0244         # 7-bit characters
0245         for i in range(0, (num_septets*7)/8+8, 7):
0246             for k in range(7):
0247                 raw[i+6-k]=msg[i+k].byte
0248         # extract the 7-bit chars
0249         for i in range(num_septets+7):
0250             tmp = (raw[(i*7)/8]<<8) | raw[((i*7)/8) + 1]
0251             bit_index = 9 - ((i*7) % 8)
0252             seven_bits[i] = (tmp >> bit_index) & 0x7f
0253         # correct the byte order and remove the data portion of the message
0254         i=0
0255         for i in range(0, num_septets+7, 8):
0256             for k in range(8):
0257                 if(i+7-k-data_len>=0):
0258                     if i+k<num_septets+7:
0259                         out[i+7-k-data_len]=seven_bits[i+k]
0260         res=""
0261         for i in range(num_septets-data_len):
0262             res+=chr(out[i])
0263         return res
0264 
0265     def _getsender(self, raw, len):
0266         result=""
0267         for i in range(len):
0268             if(raw[i].byte==10):
0269                 result+="0"
0270             else:
0271                 result+="%d" % raw[i].byte
0272         return result
0273 
0274     def getcallhistory(self, result):
0275         res={}
0276         # read the incoming call history file
0277         # the telus lg8100 programmers were on something when they wrote their code.
0278         if hasattr(self.protocolclass, 'this_takes_the_prize_for_the_most_brain_dead_call_history_file_naming_ive_seen'):
0279             self._readhistoryfile("pim/missed_log.dat", 'Incoming', res)
0280             self._readhistoryfile("pim/outgoing_log.dat", 'Missed', res)
0281             self._readhistoryfile("pim/incoming_log.dat", 'Outgoing', res)
0282         else:
0283             self._readhistoryfile("pim/missed_log.dat", 'Missed', res)
0284             self._readhistoryfile("pim/outgoing_log.dat", 'Outgoing', res)
0285             self._readhistoryfile("pim/incoming_log.dat", 'Incoming', res)
0286         result['call_history']=res
0287         return result
0288 
0289     def _readhistoryfile(self, fname, folder, res):
0290         try:
0291             buf=prototypes.buffer(self.getfilecontents(fname))
0292             ch=self.protocolclass.callhistory()
0293             ch.readfrombuffer(buf, logtitle="Call History")
0294             for call_idx in range(ch.numcalls):
0295                 call=ch.calls[call_idx]
0296                 if call.number=='' and call.name=='':
0297                         continue
0298                 entry=call_history.CallHistoryEntry()
0299                 entry.folder=folder
0300                 if call.duration:
0301                     entry.duration=call.duration
0302                 entry.datetime=((call.GPStime))
0303                 if call.number=='': # restricted calls have no number
0304                     entry.number=call.name
0305                 else:
0306                     entry.number=call.number
0307                     if call.name:
0308                         entry.name=call.name
0309                 res[entry.id]=entry
0310         except (com_brew.BrewNoSuchFileException,
0311                 IndexError):
0312             pass # do nothing if file doesn't exist or is corrupted
0313         return
0314 
0315     def getwallpaperindices(self, results):
0316         return self.getmediaindex(self.builtinimages, self.imagelocations, results, 'wallpaper-index')
0317 
0318     def getringtoneindices(self, results):
0319         return self.getmediaindex(self.builtinringtones, self.ringtonelocations, results, 'ringtone-index')
0320 
0321     def getphonebook(self,result):
0322         """Reads the phonebook data.  The L{getfundamentals} information will
0323         already be in result."""
0324         # Read speed dials first
0325         speeds={}
0326         try:
0327             if self.protocolclass.NUMSPEEDDIALS:
0328                 self.log("Reading speed dials")
0329                 buf=prototypes.buffer(self.getfilecontents("pim/pbspeed.dat"))
0330                 sd=self.protocolclass.speeddials()
0331                 sd.readfrombuffer(buf, logtitle="Read speed dials")
0332                 for i in range(self.protocolclass.FIRSTSPEEDDIAL, self.protocolclass.LASTSPEEDDIAL+1):
0333                     if sd.speeddials[i].entry<0 or sd.speeddials[i].entry>self.protocolclass.NUMPHONEBOOKENTRIES:
0334                         continue
0335                     l=speeds.get(sd.speeddials[i].entry, [])
0336                     l.append((i, sd.speeddials[i].number))
0337                     speeds[sd.speeddials[i].entry]=l
0338         except com_brew.BrewNoSuchFileException:
0339             pass
0340 
0341         pbook={}
0342         # Bug in the phone.  if you repeatedly read the phone book it starts
0343         # returning a random number as the number of entries.  We get around
0344         # this by switching into brew mode which clears that.
0345         self.mode=self.MODENONE
0346         self.setmode(self.MODEBREW)
0347         self.log("Reading number of phonebook entries")
0348         req=self.protocolclass.pbinforequest()
0349         res=self.sendpbcommand(req, self.protocolclass.pbinforesponse)
0350         numentries=res.numentries
0351         if numentries<0 or numentries>1000:
0352             self.log("The phone is lying about how many entries are in the phonebook so we are doing it the hard way")
0353             numentries=0
0354             firstserial=None
0355             loop=xrange(0,1000)
0356             hardway=True
0357         else:
0358             self.log("There are %d entries" % (numentries,))
0359             loop=xrange(0, numentries)
0360             hardway=False
0361         # reset cursor
0362         self.sendpbcommand(self.protocolclass.pbinitrequest(), self.protocolclass.pbinitresponse)
0363         problemsdetected=False
0364         dupecheck={}
0365         for i in loop:
0366             try:
0367                 if hardway:
0368                     numentries+=1
0369                 req=self.protocolclass.pbreadentryrequest()
0370                 res=self.sendpbcommand(req, self.protocolclass.pbreadentryresponse)
0371                 self.log("Read entry "+`i`+" - "+res.entry.name)
0372                 entry=self.extractphonebookentry(res.entry, speeds, result)
0373                 if hardway and firstserial is None:
0374                     firstserial=res.entry.serial1
0375                 pbook[i]=entry
0376                 if res.entry.serial1 in dupecheck:
0377                     self.log("Entry %s has same serial as entry %s.  This will cause problems." % (`entry`, dupecheck[res.entry.serial1]))
0378                     problemsdetected=True
0379                 else:
0380                     dupecheck[res.entry.serial1]=entry
0381                 self.progress(i, numentries, res.entry.name)
0382             except common.PhoneBookBusyException:
0383                 raise
0384             except Exception, e:
0385                 # Something's wrong with this entry, log it and skip
0386                 self.log('Failed to read entry %d'%i)
0387                 self.log('Exception %s raised'%`e`)
0388                 if __debug__:
0389                     raise
0390             #### Advance to next entry
0391             req=self.protocolclass.pbnextentryrequest()
0392             res=self.sendpbcommand(req, self.protocolclass.pbnextentryresponse)
0393             if hardway:
0394                 # look to see if we have looped
0395                 if res.serial==firstserial or res.serial==0:
0396                     break
0397 
0398         self.progress(numentries, numentries, "Phone book read completed")
0399 
0400         if problemsdetected:
0401             self.log("There are duplicate serial numbers.  See above for details.")
0402             raise common.IntegrityCheckFailed(self.desc, "Data in phonebook is inconsistent.  There are multiple entries with the same serial number.  See the log.")
0403 
0404         result['phonebook']=pbook
0405         cats=[]
0406         for i in result['groups']:
0407             if result['groups'][i]['name']!='No Group':
0408                 cats.append(result['groups'][i]['name'])
0409         result['categories']=cats
0410         print "returning keys",result.keys()
0411         return pbook
0412 
0413     def getgroups(self, results):
0414         self.log("Reading group information")
0415         buf=prototypes.buffer(self.getfilecontents("pim/pbgroup.dat"))
0416         g=self.protocolclass.pbgroups()
0417         g.readfrombuffer(buf, logtitle="Groups read")
0418         groups={}
0419         for i in range(len(g.groups)):
0420             if len(g.groups[i].name): # sometimes have zero length names
0421                 groups[i]={ 'icon': g.groups[i].icon, 'name': g.groups[i].name }
0422         results['groups']=groups
0423         return groups
0424 
0425     def savegroups(self, data):
0426         groups=data['groups']
0427         keys=groups.keys()
0428         keys.sort()
0429 
0430         g=self.protocolclass.pbgroups()
0431         for k in keys:
0432             e=self.protocolclass.pbgroup()
0433             e.icon=groups[k]['icon']
0434             e.name=groups[k]['name']
0435             g.groups.append(e)
0436         buffer=prototypes.buffer()
0437         g.writetobuffer(buffer, logtitle="New group file")
0438         self.writefile("pim/pbgroup.dat", buffer.getvalue())
0439 
0440     def savephonebook(self, data):
0441         "Saves out the phonebook"
0442         self.savegroups(data)
0443  
0444         progressmax=len(data['phonebook'].keys())
0445         # if we are going to write out speeddials, we have to re-read the entire
0446         # phonebook again
0447         if data.get('speeddials',None) is not None:
0448             progressmax+=len(data['phonebook'].keys())
0449 
0450         # To write the phone book, we scan through all existing entries
0451         # and record their record number and serials.
0452         # We then delete any entries that aren't in data
0453         # We then write out our records, using overwrite or append
0454         # commands as necessary
0455         serialupdates=[]
0456         existingpbook={} # keep track of the phonebook that is on the phone
0457         self.mode=self.MODENONE
0458         self.setmode(self.MODEBREW) # see note in getphonebook() for why this is necessary
0459         self.setmode(self.MODEPHONEBOOK)
0460         # similar loop to reading
0461         req=self.protocolclass.pbinforequest()
0462         res=self.sendpbcommand(req, self.protocolclass.pbinforesponse)
0463         numexistingentries=res.numentries
0464         if numexistingentries<0 or numexistingentries>1000:
0465             self.log("The phone is lying about how many entries are in the phonebook so we are doing it the hard way")
0466             numexistingentries=0
0467             firstserial=None
0468             loop=xrange(0,1000)
0469             hardway=True
0470         else:
0471             self.log("There are %d existing entries" % (numexistingentries,))
0472             progressmax+=numexistingentries
0473             loop=xrange(0, numexistingentries)
0474             hardway=False
0475         progresscur=0
0476         # reset cursor
0477         self.sendpbcommand(self.protocolclass.pbinitrequest(), self.protocolclass.pbinitresponse)
0478         for i in loop:
0479             ### Read current entry
0480             if hardway:
0481                 numexistingentries+=1
0482                 progressmax+=1
0483             req=self.protocolclass.pbreadentryrequest()
0484             res=self.sendpbcommand(req, self.protocolclass.pbreadentryresponse)
0485             
0486             entry={ 'number':  res.entry.entrynumber, 'serial1':  res.entry.serial1,
0487                     'serial2': res.entry.serial2, 'name': res.entry.name}
0488             assert entry['serial1']==entry['serial2'] # always the same
0489             self.log("Reading entry "+`i`+" - "+entry['name'])
0490             if hardway and firstserial is None:
0491                 firstserial=res.entry.serial1
0492             existingpbook[i]=entry 
0493             self.progress(progresscur, progressmax, "existing "+entry['name'])
0494             #### Advance to next entry
0495             req=self.protocolclass.pbnextentryrequest()
0496             res=self.sendpbcommand(req, self.protocolclass.pbnextentryresponse)
0497             progresscur+=1
0498             if hardway:
0499                 # look to see if we have looped
0500                 if res.serial==firstserial or res.serial==0:
0501                     break
0502         # we have now looped around back to begining
0503 
0504         # Find entries that have been deleted
0505         pbook=data['phonebook']
0506         dellist=[]
0507         for i in range(0, numexistingentries):
0508             ii=existingpbook[i]
0509             serial=ii['serial1']
0510             item=self._findserial(serial, pbook)
0511             if item is None:
0512                 dellist.append(i)
0513 
0514         progressmax+=len(dellist) # more work to do
0515 
0516         # Delete those entries
0517         for i in dellist:
0518             progresscur+=1
0519             numexistingentries-=1  # keep count right
0520             ii=existingpbook[i]
0521             self.log("Deleting entry "+`i`+" - "+ii['name'])
0522             req=self.protocolclass.pbdeleteentryrequest()
0523             req.serial1=ii['serial1']
0524             req.serial2=ii['serial2']
0525             req.entrynumber=ii['number']
0526             self.sendpbcommand(req, self.protocolclass.pbdeleteentryresponse)
0527             self.progress(progresscur, progressmax, "Deleting "+ii['name'])
0528             # also remove them from existingpbook
0529             del existingpbook[i]
0530 
0531         # counter to keep track of record number (otherwise appends don't work)
0532         counter=0
0533         # Now rewrite out existing entries
0534         keys=existingpbook.keys()
0535         existingserials=[]
0536         keys.sort()  # do in same order as existingpbook
0537         for i in keys:
0538             progresscur+=1
0539             ii=pbook[self._findserial(existingpbook[i]['serial1'], pbook)]
0540             self.log("Rewriting entry "+`i`+" - "+ii['name'])
0541             self.progress(progresscur, progressmax, "Rewriting "+ii['name'])
0542             entry=self.makeentry(counter, ii, data)
0543             counter+=1
0544             existingserials.append(existingpbook[i]['serial1'])
0545             req=self.protocolclass.pbupdateentryrequest()
0546             req.entry=entry
0547             res=self.sendpbcommand(req, self.protocolclass.pbupdateentryresponse)
0548             serialupdates.append( ( ii["bitpimserial"],
0549                                     {'sourcetype': self.serialsname, 'serial1': res.serial1, 'serial2': res.serial1,
0550                                      'sourceuniqueid': data['uniqueserial']})
0551                                   )
0552             assert ii['serial1']==res.serial1 # serial should stay the same
0553 
0554         # Finally write out new entries
0555         keys=pbook.keys()
0556         keys.sort()
0557         for i in keys:
0558             try:
0559                 ii=pbook[i]
0560                 if ii['serial1'] in existingserials:
0561                     continue # already wrote this one out
0562                 progresscur+=1
0563                 entry=self.makeentry(counter, ii, data)
0564                 counter+=1
0565                 self.log("Appending entry "+ii['name'])
0566                 self.progress(progresscur, progressmax, "Writing "+ii['name'])
0567                 req=self.protocolclass.pbappendentryrequest()
0568                 req.entry=entry
0569                 res=self.sendpbcommand(req, self.protocolclass.pbappendentryresponse)
0570                 serialupdates.append( ( ii["bitpimserial"],
0571                                          {'sourcetype': self.serialsname, 'serial1': res.newserial, 'serial2': res.newserial,
0572                                          'sourceuniqueid': data['uniqueserial']})
0573                                   )
0574             except:
0575                 self.log('Failed to write entry: '+ii['name'])
0576                 if __debug__:
0577                     raise
0578         data["serialupdates"]=serialupdates
0579         # deal with the speeddials
0580         if data.get("speeddials",None) is not None:
0581             # Yes, we have to read the ENTIRE phonebook again.  This
0582             # is because we don't know which entry numbers actually
0583             # got assigned to the various entries, and we need the
0584             # actual numbers to assign to the speed dials
0585             newspeeds={}
0586             if len(data['speeddials']):
0587                 # Move cursor to begining of phonebook
0588                 self.mode=self.MODENONE
0589                 self.setmode(self.MODEBREW) # see note in getphonebook() for why this is necessary
0590                 self.setmode(self.MODEPHONEBOOK)
0591                 self.log("Searching for speed dials")
0592                 self.sendpbcommand(self.protocolclass.pbinitrequest(), self.protocolclass.pbinitresponse)
0593                 for i in range(len(pbook)):
0594                     ### Read current entry
0595                     req=self.protocolclass.pbreadentryrequest()
0596                     res=self.sendpbcommand(req, self.protocolclass.pbreadentryresponse)
0597                     self.log("Scanning "+res.entry.name)
0598                     progresscur+=1
0599                     # we have to turn the entry serial number into a bitpim serial
0600                     serial=res.entry.serial1
0601                     found=False
0602                     for bps, serials in serialupdates:
0603                         if serials['serial1']==serial:
0604                             # found the entry
0605                             for sd in data['speeddials']:
0606                                 xx=data['speeddials'][sd]
0607                                 if xx[0]==bps:
0608                                     found=True
0609                                     if(getattr(self.protocolclass, 'SPEEDDIALINDEX', 0)==0):
0610                                         newspeeds[sd]=(res.entry.entrynumber, xx[1])
0611                                     else:
0612                                         newspeeds[sd]=(res.entry.entrynumber, res.entry.numbertypes[xx[1]].numbertype)
0613                                     nt=self.protocolclass.numbertypetab[res.entry.numbertypes[xx[1]].numbertype]
0614                                     self.log("Speed dial #%d = %s (%s/%d)" % (sd, res.entry.name, nt, xx[1]))
0615                                     self.progress(progresscur, progressmax, "Speed dial #%d = %s (%s/%d)" % (sd, res.entry.name, nt, xx[1]))
0616                     if not found:
0617                         self.progress(progresscur, progressmax, "Scanning "+res.entry.name)
0618                     # move to next entry
0619                     self.sendpbcommand(self.protocolclass.pbnextentryrequest(), self.protocolclass.pbnextentryresponse)
0620 
0621             self.progress(progressmax, progressmax, "Finished scanning")
0622             print "new speed dials is",newspeeds
0623             req=self.protocolclass.speeddials()
0624             for i in range(self.protocolclass.NUMSPEEDDIALS):
0625                 sd=self.protocolclass.speeddial()
0626                 if i in newspeeds:
0627                     sd.entry=newspeeds[i][0]
0628                     sd.number=newspeeds[i][1]
0629                 req.speeddials.append(sd)
0630             buffer=prototypes.buffer()
0631             req.writetobuffer(buffer, logtitle="New speed dials")
0632 
0633             # We check the existing speed dial file as changes require a reboot
0634             self.log("Checking existing speed dials")
0635             if buffer.getvalue()!=self.getfilecontents("pim/pbspeed.dat"):
0636                 self.writefile("pim/pbspeed.dat", buffer.getvalue())
0637                 self.log("Your phone has to be rebooted due to the speed dials changing")
0638                 self.progress(progressmax, progressmax, "Rebooting phone")
0639                 data["rebootphone"]=True
0640             else:
0641                 self.log("No changes to speed dials")
0642 
0643         return data
0644         
0645 
0646     def _findserial(self, serial, dict):
0647         """Searches dict to find entry with matching serial.  If not found,
0648         returns None"""
0649         for i in dict:
0650             if dict[i]['serial1']==serial:
0651                 return i
0652         return None
0653             
0654     def _normaliseindices(self, d):
0655         "turn all negative keys into positive ones for index"
0656         res={}
0657         keys=d.keys()
0658         keys.sort()
0659         keys.reverse()
0660         for k in keys:
0661             if k<0:
0662                 for c in range(999999):
0663                     if c not in keys and c not in res:
0664                         break
0665                 res[c]=d[k]
0666             else:
0667                 res[k]=d[k]
0668         return res
0669 
0670     def extractphonebookentry(self, entry, speeds, fundamentals):
0671         """Return a phonebook entry in BitPim format.  This is called from getphonebook."""
0672         res={}
0673         # serials
0674         res['serials']=[ {'sourcetype': self.serialsname, 'serial1': entry.serial1, 'serial2': entry.serial2,
0675                           'sourceuniqueid': fundamentals['uniqueserial']} ] 
0676         # only one name
0677         res['names']=[ {'full': entry.name} ]
0678         # only one category
0679         cat=fundamentals['groups'].get(entry.group, {'name': "No Group"})['name']
0680         if cat!="No Group":
0681             res['categories']=[ {'category': cat} ]
0682         # emails
0683         res['emails']=[]
0684         for i in entry.emails:
0685             if len(i.email):
0686                 res['emails'].append( {'email': i.email} )
0687         if not len(res['emails']): del res['emails'] # it was empty
0688         # urls
0689         if 'url' in entry.getfields() and len(entry.url):
0690             res['urls']=[ {'url': entry.url} ]
0691         # private
0692         if 'secret' in entry.getfields() and entry.secret:
0693             # we only supply secret if it is true
0694             res['flags']=[ {'secret': entry.secret } ]
0695         # memos
0696         if  'memo' in entry.getfields() and len(entry.memo):
0697             res['memos']=[ {'memo': entry.memo } ]
0698         # wallpapers
0699         if entry.wallpaper!=self.protocolclass.NOWALLPAPER:
0700             try:
0701                 paper=fundamentals['wallpaper-index'][entry.wallpaper]['name']
0702                 res['wallpapers']=[ {'wallpaper': paper, 'use': 'call'} ]                
0703             except:
0704                 print "can't find wallpaper for index",entry.wallpaper
0705                 pass
0706             
0707         # ringtones
0708         res['ringtones']=[]
0709         if 'ringtone' in entry.getfields() and entry.ringtone!=self.protocolclass.NORINGTONE:
0710             try:
0711                 tone=fundamentals['ringtone-index'][entry.ringtone]['name']
0712                 res['ringtones'].append({'ringtone': tone, 'use': 'call'})
0713             except:
0714                 print "can't find ringtone for index",entry.ringtone
0715         if 'msgringtone' in entry.getfields() and entry.msgringtone!=self.protocolclass.NOMSGRINGTONE:
0716             try:
0717                 tone=fundamentals['ringtone-index'][entry.msgringtone]['name']
0718                 res['ringtones'].append({'ringtone': tone, 'use': 'message'})
0719             except:
0720                 print "can't find ringtone for index",entry.msgringtone
0721         if len(res['ringtones'])==0:
0722             del res['ringtones']
0723         if(getattr(self.protocolclass, 'SPEEDDIALINDEX', 0)==0):
0724             res=self._assignpbtypeandspeeddialsbyposition(entry, speeds, res)
0725         else:
0726             res=self._assignpbtypeandspeeddialsbytype(entry, speeds, res)
0727         return res
0728                     
0729     def _assignpbtypeandspeeddialsbyposition(self, entry, speeds, res):
0730         # numbers
0731         res['numbers']=[]
0732         for i in range(self.protocolclass.NUMPHONENUMBERS):
0733             num=entry.numbers[i].number
0734             type=entry.numbertypes[i].numbertype
0735             if len(num):
0736                 t=self.protocolclass.numbertypetab[type]
0737                 if t[-1]=='2':
0738                     t=t[:-1]
0739                 res['numbers'].append({'number': num, 'type': t})
0740         # speed dials
0741         if entry.entrynumber in speeds:
0742             for speeddial,numberindex in speeds[entry.entrynumber]:
0743                 try:
0744                     res['numbers'][numberindex]['speeddial']=speeddial
0745                 except IndexError:
0746                     print "speed dial refers to non-existent number\n",res['numbers'],"\n",numberindex,speeddial
0747         return res
0748 
0749     def _assignpbtypeandspeeddialsbytype(self, entry, speeds, res):
0750         # for some phones (e.g. vx8100) the speeddial numberindex is really the numbertype (now why would LG want to change this!)
0751         _sd_list=speeds.get(entry.entrynumber, [])
0752         _sd_dict={}
0753         for _sd in _sd_list:
0754             _sd_dict[_sd[1]]=_sd[0]
0755         res['numbers']=[]
0756         for i in range(self.protocolclass.NUMPHONENUMBERS):
0757             num=entry.numbers[i].number
0758             type=entry.numbertypes[i].numbertype
0759             if len(num):
0760                 t=self.protocolclass.numbertypetab[type]
0761                 if t[-1]=='2':
0762                     t=t[:-1]
0763                 res['numbers'].append({'number': num, 'type': t})
0764                 # if this is a speeddial number set it
0765                 if _sd_dict.get(type, None):
0766                     res['numbers'][i]['speeddial']=_sd_dict[type]
0767         return res
0768 
0769     def _findmediainindex(self, index, name, pbentryname, type):
0770         if type=="ringtone": default=self.protocolclass.NORINGTONE
0771         elif type=="message ringtone": default=self.protocolclass.NOMSGRINGTONE
0772         elif type=="wallpaper": default=self.protocolclass.NOWALLPAPER
0773         else:
0774             assert False, "unknown type "+type
0775             
0776         if name is None:
0777             return default
0778         for i in index:
0779             if index[i]['name']==name:
0780                 return i
0781         self.log("%s: Unable to find %s %s in the index. Setting to default." % (pbentryname, type, name))
0782         return default
0783                     
0784     def makeentry(self, counter, entry, data):
0785         """Creates pbentry object
0786 
0787         @param counter: The new entry number
0788         @param entry:   The phonebook object (as returned from convertphonebooktophone) that we
0789                         are using as the source
0790         @param data:    The main dictionary, which we use to get access to media indices amongst
0791                         other things
0792                         """
0793         e=self.protocolclass.pbentry()
0794         e.entrynumber=counter
0795         
0796         for k in entry:
0797             # special treatment for lists
0798             if k in ('emails', 'numbers', 'numbertypes'):
0799                 l=getattr(e,k)
0800                 for item in entry[k]:
0801                     l.append(item)
0802             elif k=='ringtone':
0803                 e.ringtone=self._findmediainindex(data['ringtone-index'], entry['ringtone'], entry['name'], 'ringtone')
0804             elif k=='msgringtone':
0805                 e.msgringtone=self._findmediainindex(data['ringtone-index'], entry['msgringtone'], entry['name'], 'message ringtone')
0806             elif k=='wallpaper':
0807                 e.wallpaper=self._findmediainindex(data['wallpaper-index'], entry['wallpaper'], entry['name'], 'wallpaper')
0808             elif k in e.getfields():
0809                 # everything else we just set
0810                 setattr(e,k,entry[k])
0811 
0812         return e
0813 
0814     def is_mode_brew(self):
0815         req=p_brew.memoryconfigrequest()
0816         respc=p_brew.memoryconfigresponse
0817         
0818         for baud in 0, 38400, 115200:
0819             if baud:
0820                 if not self.comm.setbaudrate(baud):
0821                     continue
0822             try:
0823                 self.sendbrewcommand(req, respc, callsetmode=False)
0824                 return True
0825             except com_phone.modeignoreerrortypes:
0826                 pass
0827         return False
0828 
0829     brew_version_txt_key='brew_version.txt'
0830     brew_version_file='brew/version.txt'
0831     lgpbinfo_key='lgpbinfo'
0832     esn_file_key='esn_file'
0833     esn_file='nvm/$SYS.ESN'
0834     my_model='VX4400'
0835     def get_detect_data(self, res):
0836         # get the data needed for detection
0837         if not res.has_key(self.brew_version_txt_key):
0838             # read the BREW version.txt file, which may contain phone model info
0839             print 'reading BREW version.txt'
0840             try:
0841                 # read this file
0842                 s=self.getfilecontents(self.brew_version_file)
0843                 res[self.brew_version_txt_key]=s
0844             except (com_brew.BrewNoSuchFileException,
0845                     com_brew.BrewBadBrewCommandException,
0846                     com_brew.BrewAccessDeniedException):
0847                 res[self.brew_version_txt_key]=None
0848             except:
0849                 if __debug__:
0850                     raise
0851                 res[self.brew_version_txt_key]=None
0852         # get pbinfo data, which also may include phone model
0853         if not res.has_key(self.lgpbinfo_key):
0854             print 'getting pbinfo'
0855             try:
0856                 req=self.protocolclass.pbinforequest()
0857                 resp=self.sendpbcommand(req, self.protocolclass.pbstartsyncresponse)
0858                 res[self.lgpbinfo_key]=resp.unknown
0859             except com_brew.BrewBadBrewCommandException:
0860                 res[self.lgpbinfo_key]=None
0861             except:
0862                 if __debug__:
0863                     raise
0864                 res[self.lgpbinfo_key]=None
0865         # attempt the get the ESN
0866         if not res.has_key(self.esn_file_key):
0867             print 'reading ESN file'
0868             try:
0869                 s=self.getfilecontents(self.esn_file)
0870                 res[self.esn_file_key]=s
0871             except:
0872                 res[self.esn_file_key]=None
0873 
0874     def get_esn(self, data=None):
0875         # return the ESN for this phone
0876         try:
0877             if data is None:
0878                 s=self.getfilecontents(self.esn_file)
0879             else:
0880                 s=data
0881             if s:
0882                 s=s[85:89]
0883                 return '%02X%02X%02X%02X'%(ord(s[3]), ord(s[2]),
0884                                            ord(s[1]), ord(s[0]))
0885         except:
0886             if __debug__:
0887                 raise
0888 
0889     def eval_detect_data(self, res):
0890         found=False
0891         if res.get(self.brew_version_txt_key, None) is not None:
0892             found=res[self.brew_version_txt_key][:len(self.my_model)]==self.my_model
0893         if not found and res.get(self.lgpbinfo_key, None):
0894             found=res[self.lgpbinfo_key].find(self.my_model)!=-1
0895         if found:
0896             res['model']=self.my_model
0897             res['manufacturer']='LG Electronics Inc'
0898             s=res.get(self.esn_file_key, None)
0899             if s:
0900                 res['esn']=self.get_esn(s)
0901     @classmethod
0902     def detectphone(_, coms, likely_ports, res, _module, _log):
0903         if not likely_ports:
0904             # cannot detect any likely ports
0905             return None
0906         for port in likely_ports:
0907             if not res.has_key(port):
0908                 res[port]={ 'mode_modem': None, 'mode_brew': None,
0909                             'manufacturer': None, 'model': None,
0910                             'firmware_version': None, 'esn': None,
0911                             'firmwareresponse': None }
0912             try:
0913                 if res[port]['mode_brew']==False or \
0914                    res[port]['model']:
0915                     # either phone is not in BREW, or a model has already
0916                     # been found, not much we can do now
0917                     continue
0918                 p=_module.Phone(_log, commport.CommConnection(_log, port, timeout=1))
0919                 if res[port]['mode_brew'] is None:
0920                     res[port]['mode_brew']=p.is_mode_brew()
0921                 if res[port]['mode_brew']:
0922                     p.get_detect_data(res[port])
0923                 p.eval_detect_data(res[port])
0924                 p.comm.close()
0925             except:
0926                 if __debug__:
0927                     raise
0928     
0929     # Calendar stuff------------------------------------------------------------
0930     def getcalendar(self,result):
0931         # Read exceptions file first
0932         try:
0933             buf=prototypes.buffer(self.getfilecontents(
0934                 self.protocolclass.cal_exception_file_name))
0935             ex=self.protocolclass.scheduleexceptionfile()
0936             ex.readfrombuffer(buf, logtitle="Calendar exceptions")
0937             exceptions={}
0938             for i in ex.items:
0939                 exceptions.setdefault(i.pos, []).append( (i.year,i.month,i.day) )
0940         except com_brew.BrewNoSuchFileException:
0941             exceptions={}
0942 
0943         # Now read schedule
0944         try:
0945             buf=prototypes.buffer(self.getfilecontents(
0946                 self.protocolclass.cal_data_file_name))
0947             if len(buf.getdata())<2:
0948                 # file is empty, and hence same as non-existent
0949                 raise com_brew.BrewNoSuchFileException()
0950             sc=self.protocolclass.schedulefile()
0951             sc.readfrombuffer(buf, logtitle="Calendar")
0952             res=self.get_cal(sc, exceptions, result.get('ringtone-index', {}))
0953         except com_brew.BrewNoSuchFileException:
0954             res={}
0955         result['calendar']=res
0956         return result
0957 
0958     def savecalendar(self, dict, merge):
0959         # ::TODO:: obey merge param
0960         # get the list of available voice alarm files
0961         voice_files={}
0962         if self._cal_has_voice_id:
0963             try:
0964                 file_list=self.listfiles(self.protocolclass.cal_dir)
0965                 for k in file_list.keys():
0966                     if k.endswith(self.protocolclass.cal_voice_ext):
0967                         voice_files[int(k[8:11])]=k
0968             except:
0969                 self.log('Failed to list Calendar Voice Files')
0970         # build the schedule file
0971         sc=self.protocolclass.schedulefile()
0972         sc_ex=self.set_cal(sc, dict.get('calendar', {}),
0973                            dict.get('ringtone-index', {}),
0974                            voice_files)
0975         buf=prototypes.buffer()
0976         sc.writetobuffer(buf, logtitle="Calendar data")
0977         self.writefile(self.protocolclass.cal_data_file_name,
0978                          buf.getvalue())
0979         # build the exceptions
0980         exceptions_file=self.protocolclass.scheduleexceptionfile()
0981         for k,l in sc_ex.items():
0982             for x in l:
0983                 _ex=self.protocolclass.scheduleexception()
0984                 _ex.pos=k
0985                 _ex.year, _ex.month, _ex.day=x
0986                 exceptions_file.items.append(_ex)
0987         buf=prototypes.buffer()
0988         exceptions_file.writetobuffer(buf, logtitle="calendar exceptions")
0989         self.writefile(self.protocolclass.cal_exception_file_name,
0990                          buf.getvalue())
0991         # clear out any alarm voice files that may have been deleted
0992         if self._cal_has_voice_id:
0993             for k,e in voice_files.items():
0994                 try:
0995                     self.rmfile(e)
0996                 except:
0997                     self.log('Failed to delete file '+e)
0998         return dict
0999 
1000     _repeat_values={
1001         protocolclass.CAL_REP_DAILY: bpcalendar.RepeatEntry.daily,
1002         protocolclass.CAL_REP_MONFRI: bpcalendar.RepeatEntry.daily,
1003         protocolclass.CAL_REP_WEEKLY: bpcalendar.RepeatEntry.weekly,
1004         protocolclass.CAL_REP_MONTHLY: bpcalendar.RepeatEntry.monthly,
1005         protocolclass.CAL_REP_YEARLY: bpcalendar.RepeatEntry.yearly
1006         }
1007 
1008     def _build_cal_repeat(self, event, exceptions):
1009         rep_val=Phone._repeat_values.get(event.repeat, None)
1010         if not rep_val:
1011             return None
1012         rep=bpcalendar.RepeatEntry(rep_val)
1013         if event.repeat==self.protocolclass.CAL_REP_MONFRI:
1014             rep.interval=rep.dow=0
1015         elif event.repeat!=self.protocolclass.CAL_REP_YEARLY:
1016             rep.interval=1
1017             rep.dow=0
1018         # do exceptions
1019         cal_ex=exceptions.get(event.pos, [])
1020         for e in cal_ex:
1021             try:
1022                 rep.add_suppressed(*e)
1023             except ValueError:
1024                 # illegal exception date
1025                 pass
1026             except:
1027                 if __debug__:
1028                     raise
1029         return rep
1030 
1031     def _get_voice_id(self, event, entry):
1032         if event.hasvoice:
1033             entry.voice=event.voiceid
1034 
1035     def _build_cal_entry(self, event, exceptions, ringtone_index):
1036         try:
1037             # return a copy of bpcalendar object based on my values
1038             # general fields
1039             entry=bpcalendar.CalendarEntry()
1040             entry.start=event.start
1041             try:
1042                 entry.end=event.end
1043             # some phones (e.g. lx5550) have unreliable end dates for "forever" events
1044             except ValueError:
1045                 entry.end=self.protocolclass.CAL_REPEAT_DATE
1046             entry.desc_loc=event.description
1047             # check for allday event
1048             if entry.start[3:]==(0, 0) and entry.end[3:]==(23, 59):
1049                 entry.allday=True
1050             # alarm
1051             if event.alarmtype:
1052                 entry.alarm=event.alarmhours*60+event.alarmminutes
1053             # ringtone
1054             rt_idx=event.ringtone
1055             # hack to account for the VX4650 weird ringtone setup
1056             if rt_idx<50:
1057                 # 1st part of builtin ringtones, need offset by 1
1058                 rt_idx+=1
1059             entry.ringtone=ringtone_index.get(rt_idx, {'name': None} )['name']
1060             # voice ID if applicable
1061             if self._cal_has_voice_id:
1062                 self._get_voice_id(event, entry)
1063             # repeat info
1064             entry.repeat=self._build_cal_repeat(event, exceptions)
1065             return entry
1066         except ValueError:
1067             # illegal date/time
1068             pass
1069         except:
1070             # this entry is hosed!
1071             if __debug__:
1072                 raise
1073 
1074     def get_cal(self, sch_file, exceptions, ringtone_index):
1075         res={}
1076         for event in sch_file.events:
1077             if event.pos==-1:   # blank entry
1078                 continue
1079             cal_event=self._build_cal_entry(event, exceptions, ringtone_index)
1080             if cal_event:
1081                 res[cal_event.id]=cal_event
1082         return res
1083 
1084     _alarm_info={
1085         -1: (protocolclass.CAL_REMINDER_NONE, 100, 100),
1086         0: (protocolclass.CAL_REMINDER_ONTIME, 0, 0),
1087         5: (protocolclass.CAL_REMINDER_5MIN, 5, 0),
1088         10: (protocolclass.CAL_REMINDER_10MIN, 10, 0),
1089         60: (protocolclass.CAL_REMINDER_1HOUR, 0, 1),
1090         1440: (protocolclass.CAL_REMINDER_1DAY, 0, 24),
1091         2880: (protocolclass.CAL_REMINDER_2DAYS, 0, 48) }
1092     _default_alarm=(protocolclass.CAL_REMINDER_NONE, 100, 100)    # default alarm is off
1093     _phone_dow={
1094         1: protocolclass.CAL_DOW_SUN,
1095         2: protocolclass.CAL_DOW_MON,
1096         4: protocolclass.CAL_DOW_TUE,
1097         8: protocolclass.CAL_DOW_WED,
1098         16: protocolclass.CAL_DOW_THU,
1099         32: protocolclass.CAL_DOW_FRI,
1100         64: protocolclass.CAL_DOW_SAT
1101         }
1102 
1103     def _set_repeat_event(self, event, entry, exceptions):
1104         rep_val=self.protocolclass.CAL_REP_NONE
1105         day_bitmap=0
1106         rep=entry.repeat
1107         if rep:
1108             rep_type=rep.repeat_type
1109             if rep_type==bpcalendar.RepeatEntry.yearly:
1110                 rep_val=self.protocolclass.CAL_REP_YEARLY
1111             else:
1112                 rep_interval=rep.interval
1113                 rep_dow=rep.dow
1114                 if rep_type==bpcalendar.RepeatEntry.daily:
1115                     if rep_interval==0:
1116                         rep_val=self.protocolclass.CAL_REP_MONFRI
1117                     elif rep_interval==1:
1118                         rep_val=self.protocolclass.CAL_REP_DAILY
1119                 elif rep_type==bpcalendar.RepeatEntry.weekly:
1120                     start_dow=1<<datetime.date(*event.start[:3]).isoweekday()%7
1121                     if (rep_dow==0 or rep_dow==start_dow) and rep_interval==1:
1122                         rep_val=self.protocolclass.CAL_REP_WEEKLY
1123                         day_bitmap=self._phone_dow.get(start_dow, 0)
1124                 elif rep_type==bpcalendar.RepeatEntry.monthly:
1125                     if rep_dow==0:
1126                         rep_val=self.protocolclass.CAL_REP_MONTHLY
1127             if rep_val!=self.protocolclass.CAL_REP_NONE:
1128                 # build exception list
1129                 if rep.suppressed:
1130                     day_bitmap|=self.protocolclass.CAL_DOW_EXCEPTIONS
1131                 for x in rep.suppressed:
1132                     exceptions.setdefault(event.pos, []).append(x.get()[:3])
1133                 # this is a repeat event, set the end date appropriately
1134                 if event.end[:3]==entry.no_end_date:
1135                     event.end=self.protocolclass.CAL_REPEAT_DATE+event.end[3:]
1136                 else:
1137                     event.end=entry.end
1138         event.repeat=rep_val
1139         event.daybitmap=day_bitmap
1140             
1141     def _set_alarm(self, event, entry):
1142         # set alarm value based on entry's value, or its approximation
1143         keys=Phone._alarm_info.keys()
1144         keys.sort()
1145         keys.reverse()
1146         _alarm_val=entry.alarm
1147         _alarm_key=None
1148         for k in keys:
1149             if _alarm_val>=k:
1150                 _alarm_key=k
1151                 break
1152         event.alarmtype, event.alarmminutes, event.alarmhours=Phone._alarm_info.get(
1153             _alarm_key, self._default_alarm)
1154 
1155     def _set_voice_id(self, event, entry, voice_files):
1156         if entry.voice and \
1157            voice_files.has_key(entry.voice-self.protocolclass.cal_voice_id_ofs):
1158             event.hasvoice=1
1159             event.voiceid=entry.voice
1160             del voice_files[entry.voice-self.protocolclass.cal_voice_id_ofs]
1161         else:
1162             event.hasvoice=0
1163             event.voiceid=self.protocolclass.CAL_NO_VOICE
1164         
1165     def _set_cal_event(self, event, entry, exceptions, ringtone_index,
1166                        voice_files):
1167         # desc
1168         event.description=entry.desc_loc
1169         # start & end times
1170         if entry.allday:
1171             event.start=entry.start[:3]+(0,0)
1172             event.end=entry.start[:3]+(23,59)
1173         else:
1174             event.start=entry.start
1175             event.end=entry.start[:3]+entry.end[3:]
1176         # make sure the event lasts in 1 calendar day
1177         if event.end<event.start:
1178             event.end=event.start[:3]+(23,59)
1179         # alarm
1180         self._set_alarm(event, entry)
1181         # ringtone
1182         rt=0    # always default to the first bultin ringtone
1183         if entry.ringtone:
1184             for k,e in ringtone_index.items():
1185                 if e['name']==entry.ringtone:
1186                     rt=k
1187                     break
1188             if rt and rt<50:
1189                 rt-=1
1190         event.ringtone=rt
1191         # voice ID
1192         if self._cal_has_voice_id:
1193             self._set_voice_id(event, entry, voice_files)
1194         # repeat
1195         self._set_repeat_event(event, entry, exceptions)
1196             
1197     def set_cal(self, sch_file, cal_dict, ringtone_index, voice_files):
1198         exceptions={}
1199         _pos=2
1200         _packet_size=None
1201 ##        _today=datetime.date.today().timetuple()[:5]
1202         _entry_cnt=0
1203         for k, e in cal_dict.items():
1204 ##            # only send either repeat events or present&future single events
1205 ##            if e.repeat or (e.start>=_today):
1206             event=self.protocolclass.scheduleevent()
1207             event.pos=_pos
1208             self._set_cal_event(event, e, exceptions, ringtone_index,
1209                                 voice_files)
1210             sch_file.events.append(event)
1211             if not _packet_size:
1212                 _packet_size=event.packetsize()
1213             _pos+=_packet_size
1214             _entry_cnt+=1
1215             if _entry_cnt>=self.protocolclass.NUMCALENDARENTRIES:
1216                 break
1217         sch_file.numactiveitems=_entry_cnt
1218         return exceptions
1219 
1220     # Text Memo stuff-----------------------------------------------------------
1221     def getmemo(self, result):
1222         # read the memo file
1223         try:
1224             buf=prototypes.buffer(self.getfilecontents(
1225                 self.protocolclass.text_memo_file))
1226             text_memo=self.protocolclass.textmemofile()
1227             text_memo.readfrombuffer(buf, logtitle="Read text memo")
1228             res={}
1229             for i in range(text_memo.itemcount):
1230                 entry=memo.MemoEntry()
1231                 entry.text=text_memo.items[i].text
1232                 res[entry.id]=entry
1233         except com_brew.BrewNoSuchFileException:
1234             res={}
1235         result['memo']=res
1236         return result
1237 
1238     def savememo(self, result, merge):
1239         text_memo=self.protocolclass.textmemofile()
1240         memo_dict=result.get('memo', {})
1241         keys=memo_dict.keys()
1242         keys.sort()
1243         text_memo.itemcount=len(keys)
1244         for k in keys:
1245             entry=self.protocolclass.textmemo()
1246             entry.text=memo_dict[k].text
1247             text_memo.items.append(entry)
1248         buf=prototypes.buffer()
1249         text_memo.writetobuffer(buf, logtitle="text memo "+self.protocolclass.text_memo_file)
1250         self.writefile(self.protocolclass.text_memo_file, buf.getvalue())
1251         return result
1252 
1253 
1254 parentprofile=com_phone.Profile
1255 class Profile(parentprofile):
1256     protocolclass=Phone.protocolclass
1257     serialsname=Phone.serialsname
1258     BP_Calendar_Version=3
1259     phone_manufacturer='LG Electronics Inc'
1260     phone_model='VX4400'
1261     brew_required=True
1262 
1263     WALLPAPER_WIDTH=120
1264     WALLPAPER_HEIGHT=98
1265     MAX_WALLPAPER_BASENAME_LENGTH=19
1266     WALLPAPER_FILENAME_CHARS="abcdefghijklmnopqrstuvwxyz0123456789 ."
1267     WALLPAPER_CONVERT_FORMAT="bmp"
1268     
1269     MAX_RINGTONE_BASENAME_LENGTH=19
1270     RINGTONE_FILENAME_CHARS="abcdefghijklmnopqrstuvwxyz0123456789 ."
1271     DIALSTRING_CHARS="[^0-9PT#*]"
1272     
1273     # which usb ids correspond to us
1274     usbids_straight=( ( 0x1004, 0x6000, 2), )# VID=LG Electronics, PID=LG VX4400/VX6000 -internal USB diagnostics interface
1275     usbids_usbtoserial=(
1276         ( 0x067b, 0x2303, None), # VID=Prolific, PID=USB to serial
1277         ( 0x0403, 0x6001, None), # VID=FTDI, PID=USB to serial
1278         ( 0x0731, 0x2003, None), # VID=Susteen, PID=Universal USB to serial
1279         )
1280     usbids=usbids_straight+usbids_usbtoserial
1281     
1282     # which device classes we are.  not we are not modem!
1283     deviceclasses=("serial",)
1284 
1285     # nb we don't allow save to camera so it isn't listed here
1286     imageorigins={}
1287     imageorigins.update(common.getkv(parentprofile.stockimageorigins, "images"))
1288     def GetImageOrigins(self):
1289         return self.imageorigins
1290 
1291     # our targets are the same for all origins
1292     imagetargets={}
1293     imagetargets.update(common.getkv(parentprofile.stockimagetargets, "wallpaper",
1294                                       {'width': 120, 'height': 98, 'format': "BMP"}))
1295     imagetargets.update(common.getkv(parentprofile.stockimagetargets, "pictureid",
1296                                       {'width': 120, 'height': 98, 'format': "BMP"}))
1297     imagetargets.update(common.getkv(parentprofile.stockimagetargets, "fullscreen",
1298                                       {'width': 120, 'height': 133, 'format': "BMP"}))
1299 
1300     def GetTargetsForImageOrigin(self, origin):
1301         return self.imagetargets
1302     
1303     def __init__(self):
1304         parentprofile.__init__(self)
1305 
1306 
1307     def _getgroup(self, name, groups):
1308         for key in groups:
1309             if groups[key]['name']==name:
1310                 return key,groups[key]
1311         return None,None
1312         
1313 
1314     def normalisegroups(self, helper, data):
1315         "Assigns groups based on category data"
1316 
1317         pad=[]
1318         keys=data['groups'].keys()
1319         keys.sort()
1320         for k in keys:
1321                 if k: # ignore key 0 which is 'No Group'
1322                     name=data['groups'][k]['name']
1323                     pad.append(name)
1324 
1325         groups=helper.getmostpopularcategories(self.protocolclass.MAX_PHONEBOOK_GROUPS, data['phonebook'], ["No Group"], 22, pad)
1326 
1327         # alpha sort
1328         groups.sort()
1329 
1330         # newgroups
1331         newgroups={}
1332 
1333         # put in No group
1334         newgroups[0]={'name': 'No Group', 'icon': 0}
1335 
1336         # populate
1337         for name in groups:
1338             # existing entries remain unchanged
1339             if name=="No Group": continue
1340             key,value=self._getgroup(name, data['groups'])
1341             if key is not None and key!=0:
1342                 newgroups[key]=value
1343         # new entries get whatever numbers are free
1344         for name in groups:
1345             key,value=self._getgroup(name, newgroups)
1346             if key is None:
1347                 for key in range(1,100000):
1348                     if key not in newgroups:
1349                         newgroups[key]={'name': name, 'icon': 1}
1350                         break
1351                        
1352         # yay, done
1353         if data['groups']!=newgroups:
1354             data['groups']=newgroups
1355             data['rebootphone']=True
1356             
1357     def convertphonebooktophone(self, helper, data):
1358         """Converts the data to what will be used by the phone
1359 
1360         @param data: contains the dict returned by getfundamentals
1361                      as well as where the results go"""
1362         results={}
1363 
1364         speeds={}
1365 
1366         self.normalisegroups(helper, data)
1367 
1368         for pbentry in data['phonebook']:
1369             if len(results)==self.protocolclass.NUMPHONEBOOKENTRIES:
1370                 break
1371             e={} # entry out
1372             entry=data['phonebook'][pbentry] # entry in
1373             try:
1374                 # serials
1375                 serial1=helper.getserial(entry.get('serials', []), self.serialsname, data['uniqueserial'], 'serial1', 0)
1376                 serial2=helper.getserial(entry.get('serials', []), self.serialsname, data['uniqueserial'], 'serial2', serial1)
1377 
1378                 e['serial1']=serial1
1379                 e['serial2']=serial2
1380                 for ss in entry["serials"]:
1381                     if ss["sourcetype"]=="bitpim":
1382                         e['bitpimserial']=ss
1383                 assert e['bitpimserial']
1384 
1385                 # name
1386                 e['name']=helper.getfullname(entry.get('names', []),1,1,22)[0]
1387 
1388                 # categories/groups
1389                 cat=helper.makeone(helper.getcategory(entry.get('categories', []),0,1,22), None)
1390                 if cat is None:
1391                     e['group']=0
1392                 else:
1393                     key,value=self._getgroup(cat, data['groups'])
1394                     if key is not None:
1395                         e['group']=key
1396                     else:
1397                         # sorry no space for this category
1398                         e['group']=0
1399 
1400                 # email addresses
1401                 emails=helper.getemails(entry.get('emails', []) ,0,self.protocolclass.NUMEMAILS,48)
1402                 e['emails']=helper.filllist(emails, self.protocolclass.NUMEMAILS, "")
1403 
1404                 # url
1405                 e['url']=helper.makeone(helper.geturls(entry.get('urls', []), 0,1,48), "")
1406 
1407                 # memo (-1 is to leave space for null terminator - not all software puts it in, but we do)
1408                 e['memo']=helper.makeone(helper.getmemos(entry.get('memos', []), 0, 1, self.protocolclass.MEMOLENGTH-1), "")
1409 
1410                 # phone numbers
1411                 # there must be at least one email address or phonenumber
1412                 minnumbers=1
1413                 if len(emails): minnumbers=0
1414                 numbers=helper.getnumbers(entry.get('numbers', []),minnumbers,self.protocolclass.NUMPHONENUMBERS)
1415                 e['numbertypes']=[]
1416                 e['numbers']=[]
1417                 for numindex in range(len(numbers)):
1418                     num=numbers[numindex]
1419                     # deal with type
1420                     b4=len(e['numbertypes'])
1421                     type=num['type']
1422                     for i,t in enumerate(self.protocolclass.numbertypetab):
1423                         if type==t:
1424                             # some voodoo to ensure the second home becomes home2
1425                             if i in e['numbertypes'] and t[-1]!='2':
1426                                 type+='2'
1427                                 continue
1428                             e['numbertypes'].append(i)
1429                             break
1430                         if t=='none': # conveniently last entry
1431                             e['numbertypes'].append(i)
1432                             break
1433                     if len(e['numbertypes'])==b4:
1434                         # we couldn't find a type for the number
1435                         helper.add_error_message('Number %s (%s/%s) not supported and ignored.'%
1436                                                  (num['number'], e['name'], num['type']))
1437                         continue 
1438                     # deal with number
1439                     number=self.phonize(num['number'])
1440                     if len(number)==0:
1441                         # no actual digits in the number
1442                         continue
1443                     if len(number)>48: # get this number from somewhere sensible
1444                         # ::TODO:: number is too long and we have to either truncate it or ignore it?
1445                         number=number[:48] # truncate for moment
1446                     e['numbers'].append(number)
1447                     # deal with speed dial
1448                     sd=num.get("speeddial", -1)
1449                     if self.protocolclass.NUMSPEEDDIALS:
1450                         if sd>=self.protocolclass.FIRSTSPEEDDIAL and sd<=self.protocolclass.LASTSPEEDDIAL:
1451                             speeds[sd]=(e['bitpimserial'], numindex)
1452 
1453                 if len(e['numbers'])<minnumbers:
1454                     # we couldn't find any numbers
1455                     # for this entry, so skip it, entries with no numbers cause error
1456                     helper.add_error_message("Name: %s. No suitable numbers or emails found" % e['name'])
1457                     continue 
1458                 e['numbertypes']=helper.filllist(e['numbertypes'], 5, 0)
1459                 e['numbers']=helper.filllist(e['numbers'], 5, "")
1460 
1461                 # ringtones, wallpaper
1462                 e['ringtone']=helper.getringtone(entry.get('ringtones', []), 'call', None)
1463                 e['msgringtone']=helper.getringtone(entry.get('ringtones', []), 'message', None)
1464                 e['wallpaper']=helper.getwallpaper(entry.get('wallpapers', []), 'call', None)
1465 
1466                 # flags
1467                 e['secret']=helper.getflag(entry.get('flags',[]), 'secret', False)
1468 
1469                 results[pbentry]=e
1470                 
1471             except helper.ConversionFailed:
1472                 continue
1473 
1474         if self.protocolclass.NUMSPEEDDIALS:
1475             data['speeddials']=speeds
1476         data['phonebook']=results
1477         return data
1478 
1479     _supportedsyncs=(
1480         ('phonebook', 'read', None),  # all phonebook reading
1481         ('calendar', 'read', None),   # all calendar reading
1482         ('wallpaper', 'read', None),  # all wallpaper reading
1483         ('ringtone', 'read', None),   # all ringtone reading
1484         ('call_history', 'read', None),# all call history list reading
1485         ('sms', 'read', None),         # all SMS list reading
1486         ('memo', 'read', None),        # all memo list reading
1487         ('phonebook', 'write', 'OVERWRITE'),  # only overwriting phonebook
1488         ('calendar', 'write', 'OVERWRITE'),   # only overwriting calendar
1489         ('wallpaper', 'write', 'MERGE'),      # merge and overwrite wallpaper
1490         ('wallpaper', 'write', 'OVERWRITE'),
1491         ('ringtone', 'write', 'MERGE'),      # merge and overwrite ringtone
1492         ('ringtone', 'write', 'OVERWRITE'),
1493         ('sms', 'write', 'OVERWRITE'),        # all SMS list writing
1494         ('memo', 'write', 'OVERWRITE'),      # all memo list writing
1495         )
1496 
1497     def QueryAudio(self, origin, currentextension, afi):
1498         # we don't modify any of these
1499         if afi.format in ("MIDI", "QCP", "PMD"):
1500             return currentextension, afi
1501         # examine mp3
1502         if afi.format=="MP3":
1503             if afi.channels==1 and 8<=afi.bitrate<=64 and 16000<=afi.samplerate<=22050:
1504                 return currentextension, afi
1505         # convert it
1506         return ("mp3", fileinfo.AudioFileInfo(afi, **{'format': 'MP3', 'channels': 1, 'bitrate': 32, 'samplerate': 22050}))
1507 
1508 
1509 

Generated by PyXR 0.9.4