PyXR

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



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

Generated by PyXR 0.9.4