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