0001 ### BITPIM 0002 ### 0003 ### Copyright (C) 2003-2007 Stephen Wood <sawecw@users.sf.net> 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_sanyo.py 4618 2008-06-19 03:55:00Z sawecw $ 0009 0010 """Phonebook conversations with Sanyo phones""" 0011 0012 # standard modules 0013 import re 0014 import time 0015 import cStringIO 0016 import sha 0017 import datetime 0018 0019 # BitPim modules 0020 import com_brew 0021 import com_phone 0022 import p_sanyo 0023 import prototypes 0024 import common 0025 import conversions 0026 import bpcalendar 0027 import call_history 0028 import sms 0029 import todo 0030 import helpids 0031 0032 numbertypetab=( 'home', 'office', 'cell', 'pager', 0033 'data', 'fax', 'none' ) 0034 0035 class SanyoCommandException(Exception): 0036 def __init__(self, errnum, str=None): 0037 if str is None: 0038 str="Sanyo Packet Error 0x%02x" % (errnum,) 0039 Exception.__init__(self, str) 0040 self.errnum=errnum 0041 0042 class SanyoPhonebook: 0043 "Talk to a Sanyo Sprint Phone such as SCP-4900, SCP-5300, or SCP-8100" 0044 0045 # phone uses Jan 1, 1980 as epoch. Python uses Jan 1, 1970. This is difference 0046 # plus a fudge factor of 5 days for no reason I can find 0047 _sanyoepochtounix=315532800+432000; 0048 0049 pbterminator="\x7e" 0050 MODEPHONEBOOK="modephonebook" # can speak the phonebook protocol 0051 0052 calendar_defaultringtone=0 0053 calendar_defaultcaringtone=0 0054 calendar_voicenumber=0 0055 phonebook_voicenumber=0 0056 0057 def __init__(self): 0058 self.numbertypetab=numbertypetab 0059 0060 def get_esn(self): 0061 req=self.protocolclass.esnrequest() 0062 res=self.sendpbcommand(req, self.protocolclass.esnresponse) 0063 return '%08X'%res.esn 0064 0065 def _setmodephonebook(self): 0066 self.setmode(self.MODEBREW) 0067 req=p_sanyo.firmwarerequest() 0068 respc=p_sanyo.firmwareresponse 0069 try: 0070 self.sendpbcommand(req, respc, callsetmode=False) 0071 return 1 0072 except com_phone.modeignoreerrortypes: 0073 pass 0074 try: 0075 self.comm.setbaudrate(38400) 0076 self.sendpbcommand(req, respc, callsetmode=False) 0077 return 1 0078 except com_phone.modeignoreerrortypes: 0079 pass 0080 return 0 0081 0082 def getmediaindices(self, results): 0083 """Get all the media indices 0084 0085 @param results: places results in this dict 0086 """ 0087 # Do all media types in one function to avoid having to scan through 0088 # directories more than once 0089 ringermedia={} 0090 imagemedia={} 0091 0092 c=1 0093 for name in self.builtinimages: 0094 if name: 0095 imagemedia[c]={'name': name, 'origin': 'builtin' } 0096 # print c,name 0097 c+=1 0098 results['wallpaper-index']=imagemedia 0099 c=1 0100 for name in self.builtinringtones: 0101 if name: 0102 ringermedia[c]={'name': name, 'origin': 'builtin' } 0103 # print c,name 0104 c+=1 0105 results['ringtone-index']=ringermedia 0106 0107 return 0108 0109 wallpaper = self.getsanyobuffer(self.protocolclass.wallpaperbuffer) 0110 for i in range(0, self.protocolclass._NUMPBSLOTS): 0111 if wallpaper.wallpapers[i].flag: 0112 print i, wallpaper.wallpapers[i].word1, wallpaper.wallpapers[i].word2 0113 0114 req=self.protocolclass.bufferpartrequest() 0115 0116 req.header.command=0xc8 0117 req.header.packettype=0x0c 0118 res=self.sendpbcommand(req, self.protocolclass.bufferpartresponse) 0119 #req.header.command=0x69 0120 #req.header.packettype=0x0f 0121 #res=self.sendpbcommand(req, self.protocolclass.bufferpartresponse) 0122 #req.header.command=0x6a 0123 #res=self.sendpbcommand(req, self.protocolclass.bufferpartresponse) 0124 #req.header.command=0x6b 0125 #res=self.sendpbcommand(req, self.protocolclass.bufferpartresponse) 0126 0127 req.header.packettype=0x0c 0128 req.header.command=0x49 0129 res=self.sendpbcommand(req, self.protocolclass.bufferpartresponse) 0130 req.header.command=0xc8 0131 res=self.sendpbcommand(req, self.protocolclass.bufferpartresponse) 0132 0133 if not self.serialsname == 'scp4900': 0134 # 4900 doesn't like this 0135 req.header.command=0xc9 0136 res=self.sendpbcommand(req, self.protocolclass.bufferpartresponse) 0137 0138 return 0139 0140 def getmediaindex(self, builtins, maps, results, key): 0141 """Gets the media (wallpaper/ringtone) index 0142 0143 @param builtins: the builtin list on the phone 0144 @param results: places results in this dict 0145 @param maps: the list of index files and locations 0146 @param key: key to place results in 0147 """ 0148 0149 self.log("Reading "+key) 0150 media={} 0151 0152 # builtins 0153 c=1 0154 for name in builtins: 0155 if name: 0156 media[c]={'name': name, 'origin': 'builtin' } 0157 # print c,name 0158 c+=1 0159 0160 results[key]=media 0161 return media 0162 0163 def getwallpaperindices(self, results): 0164 return self.getmediaindex(self.builtinimages, self.imagelocations, results, 'wallpaper-index') 0165 0166 def getringtoneindices(self, results): 0167 return self.getmediaindex(self.builtinringtones, self.ringtonelocations, results, 'ringtone-index') 0168 0169 def getsanyobuffer(self, responseclass): 0170 # Read buffer parts and concatenate them together 0171 bufres=responseclass() 0172 desc="Reading "+bufres.comment 0173 data=cStringIO.StringIO() 0174 bufp=0 0175 command=bufres.startcommand 0176 buffersize=bufres.bufsize 0177 req=self.protocolclass.bufferpartrequest() 0178 if command==0xd7: 0179 req.header.packettype=0x0c # Special Case for 5500 Class phones 0180 for offset in range(0, buffersize, req.bufpartsize): 0181 self.progress(data.tell(), buffersize, desc) 0182 req.header.command=command 0183 res=self.sendpbcommand(req, self.protocolclass.bufferpartresponse); 0184 data.write(res.data) 0185 command+=1 0186 0187 self.progress(1,1,desc) 0188 0189 data=data.getvalue() 0190 self.log("expected size "+`buffersize`+" actual "+`len(data)`) 0191 assert buffersize==len(data) 0192 buffer=prototypes.buffer(data) 0193 bufres.readfrombuffer(buffer, logtitle="Sanyo buffer response") 0194 return bufres 0195 0196 def sendsanyobuffer(self, bufreq): 0197 comment=bufreq.comment 0198 buffer=prototypes.buffer() 0199 bufreq.writetobuffer(buffer, logtitle="Send sanyo buffer") 0200 buffer=buffer.getvalue() 0201 self.log("Writing "+comment+" "+` len(buffer) `+" bytes") 0202 desc="Writing "+comment 0203 req=self.protocolclass.bufferpartupdaterequest() 0204 bufpartsize=req.bufpartsize 0205 numblocks=len(buffer)/bufpartsize 0206 offset=0 0207 command=bufreq.startcommand 0208 if command==0xd7: 0209 req.header.packettype=0x0c 0210 for offset in range(0, len(buffer), bufpartsize): 0211 self.progress(offset/bufpartsize, numblocks, desc) 0212 req.header.command=command 0213 block=buffer[offset:] 0214 l=min(len(block), bufpartsize) 0215 block=block[:l] 0216 req.data=block 0217 command+=1 0218 self.sendpbcommand(req, self.protocolclass.bufferpartresponse, writemode=True) 0219 0220 def sendpbcommand(self, request, responseclass, callsetmode=True, writemode=False, numsendretry=0, returnerror=False): 0221 if writemode: 0222 numretry=3 0223 else: 0224 numretry=0 0225 0226 if callsetmode: 0227 self.setmode(self.MODEPHONEBOOK) 0228 buffer=prototypes.buffer() 0229 0230 request.writetobuffer(buffer, logtitle="Sanyo phonebook request") 0231 data=buffer.getvalue() 0232 firsttwo=data[:2] 0233 data=common.pppescape(data+common.crcs(data))+common.pppterminator 0234 isendretry=numsendretry 0235 while isendretry>=0: 0236 try: 0237 rdata=self.comm.writethenreaduntil(data, False, common.pppterminator, logreaduntilsuccess=False, numfailures=numretry) 0238 break 0239 except com_phone.modeignoreerrortypes: 0240 if isendretry>0: 0241 self.log("Resending request packet...") 0242 time.sleep(0.3) 0243 else: 0244 self.comm.success=False 0245 self.mode=self.MODENONE 0246 self.raisecommsdnaexception("manipulating the phonebook") 0247 isendretry-=1 0248 0249 self.comm.success=True 0250 0251 origdata=rdata 0252 # sometimes there is junk at the beginning, eg if the user 0253 # turned off the phone and back on again. So if there is more 0254 # than one 7e in the escaped data we should start after the 0255 # second to last one 0256 d=rdata.rfind(common.pppterminator,0,-1) 0257 if d>=0: 0258 self.log("Multiple Sanyo packets in data - taking last one starting at "+`d+1`) 0259 self.logdata("Original Sanyo data", origdata, None) 0260 rdata=rdata[d+1:] 0261 0262 # turn it back to normal 0263 data=common.pppunescape(rdata) 0264 0265 # Sometimes there is other crap at the beginning. But it might 0266 # be a Sanyo error byte. So strip off bytes from the beginning 0267 # until the crc agrees, or we get to the first two bytes of the 0268 # request packet. 0269 d=data.find(firsttwo) 0270 crc=data[-3:-1] 0271 crcok=False 0272 for i in range(0,d+1): 0273 trydata=data[i:-3] 0274 if common.crcs(trydata)==crc: 0275 crcok=True 0276 break 0277 0278 if not crcok: 0279 self.logdata("first two",firsttwo, None) 0280 self.logdata("Original Sanyo data", origdata, None) 0281 self.logdata("Working on Sanyo data", data, None) 0282 raise common.CommsDataCorruption("Sanyo packet failed CRC check", self.desc) 0283 0284 res=responseclass() 0285 if d>0: 0286 if d==i: 0287 self.log("Junk at beginning of Sanyo packet, data at "+`d`) 0288 self.logdata("Original Sanyo data", origdata, None) 0289 self.logdata("Working on Sanyo data", data, None) 0290 else: 0291 if returnerror: 0292 res=self.protocolclass.sanyoerror() 0293 else: 0294 self.log("Sanyo Error code "+`ord(data[0])`) 0295 self.logdata("sanyo phonebook response", data, None) 0296 raise SanyoCommandException(ord(data[0])) 0297 0298 data=trydata 0299 0300 # parse data 0301 buffer=prototypes.buffer(data) 0302 res.readfrombuffer(buffer, logtitle="sanyo phonebook response") 0303 return res 0304 0305 def getfundamentals(self, results): 0306 """Gets information fundamental to interopating with the phone and UI.""" 0307 0308 # use a hash of ESN and other stuff (being paranoid) 0309 print "HASRINGPICBUF=",self.protocolclass.HASRINGPICBUF 0310 self.log("Retrieving fundamental phone information") 0311 self.log("Phone serial number") 0312 results['uniqueserial']=sha.new(self.getfilecontents("nvm/$SYS.ESN")).hexdigest() 0313 0314 #self.getwallpaperindices(results) 0315 #self.getringtoneindices(results) 0316 self.getmediaindices(results) 0317 self.log("Fundamentals retrieved") 0318 return results 0319 0320 def sanyosort(self, a, b): 0321 "Sanyo sort order. Case insensitive, letters first" 0322 x=a[1] 0323 y=b[1] 0324 # This is right only for first column 0325 if(x[0:1].isalpha() and not y[0:1].isalpha()): 0326 return -1 0327 if(y[0:1].isalpha() and not x[0:1].isalpha()): 0328 return 1 0329 return cmp(x.lower(), y.lower()) 0330 0331 def getsms(self, result): 0332 gsms = {} 0333 self.log("Getting sms entries") 0334 req=self.protocolclass.messagerequest() 0335 for box in range(2): 0336 if box==0: 0337 req=self.protocolclass.messagerequest() 0338 respc=self.protocolclass.messageresponse 0339 else: 0340 req=self.protocolclass.messagesentrequest() 0341 respc=self.protocolclass.messagesentresponse 0342 for slot in range(self.protocolclass.NUMMESSAGESLOTS): 0343 req.slot=slot 0344 res=self.sendpbcommand(req, respc) 0345 if res.entry.dunno4==2: 0346 entry=sms.SMSEntry() 0347 entry.datetime="200%d%02d%02dT%02d%02d%02d" % ((res.entry.year, res.entry.month, res.entry.day, res.entry.hour,res.entry.minute, res.entry.second)) 0348 if box==0: 0349 entry.folder=entry.Folder_Inbox 0350 entry._from=res.entry.phonenum 0351 else: 0352 entry.folder=entry.Folder_Sent 0353 entry._to=res.entry.phonenum 0354 #entry.add_recipient(res.entry.phonenum,confirmed=True) 0355 if res.entry.read==17: 0356 entry.read=1 0357 else: 0358 entry.read=0 0359 entry.callback=res.entry.callback 0360 entry.subject=res.entry.message[:12] 0361 self.log(res.entry.message[:8]) 0362 if res.entry.priority==100: 0363 entry.priority=sms.SMSEntry.Priority_Normal 0364 if res.entry.priority==200: 0365 entry.priority=sms.SMSEntry.Priority_High 0366 entry.text=res.entry.message 0367 gsms[entry.id]=entry 0368 result['sms']=gsms 0369 return result 0370 0371 def gettodo(self, result): 0372 gtodo = {} 0373 self.log("Getting todo entries") 0374 req=self.protocolclass.todorequest() 0375 for slot in range(self.protocolclass.NUMTODOSLOTS): 0376 req.slot=slot 0377 res=self.sendpbcommand(req, self.protocolclass.todoresponse) 0378 entry=todo.TodoEntry() 0379 if res.entry.flag: 0380 entry.summary=res.entry.todo 0381 if res.entry.priority==2: 0382 entry.status=4 0383 if res.entry.priority==0: 0384 entry.priority=5 0385 if res.entry.priority==1: 0386 entry.priority=1 0387 gtodo[entry.id]=entry 0388 result['todo']=gtodo 0389 return result 0390 0391 def getphonebook(self,result): 0392 pbook={} 0393 # Get Sort buffer so we know which of the 300 phone book slots 0394 # are in use. 0395 sortstuff = self.getsanyobuffer(self.protocolclass.pbsortbuffer) 0396 0397 # Get the ringer and wall paper assignments 0398 if self.protocolclass.HASRINGPICBUF: 0399 ringpic = self.getsanyobuffer(self.protocolclass.ringerpicbuffer) 0400 0401 speedslot=[] 0402 speedtype=[] 0403 for i in range(self.protocolclass._NUMSPEEDDIALS): 0404 speedslot.append(sortstuff.speeddialindex[i].pbslotandtype & 0xfff) 0405 numtype=(sortstuff.speeddialindex[i].pbslotandtype>>12)-1 0406 if(numtype >= 0 and numtype <= len(self.numbertypetab)): 0407 speedtype.append(self.numbertypetab[numtype]) 0408 else: 0409 speedtype.append("") 0410 0411 numentries=sortstuff.slotsused 0412 self.log("There are %d entries" % (numentries,)) 0413 0414 count = 0 # Number of phonebook entries 0415 numcount = 0 # Number of phone numbers 0416 numemail = 0 # Number of emails 0417 numurl = 0 # Number of urls 0418 res=self.protocolclass.phonebookentry() 0419 usedefaultnum = 'defaultnum' in res.getfields() 0420 print "usedefaultnum =",usedefaultnum 0421 0422 req=self.protocolclass.phonebookslotrequest() 0423 for i in range(0, self.protocolclass._NUMPBSLOTS): 0424 if sortstuff.usedflags[i].used: 0425 ### Read current entry 0426 req.slot = i 0427 res=self.sendpbcommand(req, self.protocolclass.phonebookslotresponse) 0428 self.log("Read entry "+`i`+" - "+res.entry.name) 0429 0430 entry=self.extractphonebookentry(res.entry, result) 0431 # Speed dials 0432 for j in range(len(speedslot)): 0433 if(speedslot[j]==req.slot): 0434 for k in range(len(entry['numbers'])): 0435 if(entry['numbers'][k]['type']==speedtype[j]): 0436 entry['numbers'][k]['speeddial']=j+2 0437 break 0438 0439 if self.protocolclass.HASRINGPICBUF: 0440 # ringtones 0441 if ringpic.ringtones[i].ringtone>0: 0442 print res.entry.name,ringpic.ringtones[i].ringtone 0443 try: 0444 tone=result['ringtone-index'][ringpic.ringtones[i].ringtone]['name'] 0445 except: 0446 tone=self.serialsname+"Index_"+`ringpic.ringtones[i].ringtone` 0447 entry['ringtones']=[{'ringtone': tone, 'use': 'call'}] 0448 0449 # wallpapers 0450 if ringpic.wallpapers[i].wallpaper>0: 0451 try: 0452 paper=result['wallpaper-index'][ringpic.wallpapers[i].wallpaper]['name'] 0453 except: 0454 paper=self.serialsname+"Index_"+`ringpic.wallpapers[i].wallpaper` 0455 entry['wallpapers']=[{'wallpaper': paper, 'use': 'call'}] 0456 0457 # Set default number, swap with first number 0458 if usedefaultnum: 0459 firsttype=res.entry.defaultnum-1 0460 if firsttype < len(self.numbertypetab): 0461 defaulttype=self.numbertypetab[firsttype] 0462 k=0 0463 for j in range(len(entry['numbers'])): 0464 if entry['numbers'][j]['type'] == defaulttype: 0465 k=j 0466 break 0467 if k>0: 0468 exchange=entry['numbers'][k] 0469 for kk in range(k,0,-1): 0470 entry['numbers'][kk]=entry['numbers'][kk-1] 0471 entry['numbers'][0]=exchange 0472 0473 pbook[count]=entry 0474 self.progress(count, numentries, res.entry.name) 0475 count+=1 0476 numcount+=len(entry['numbers']) 0477 if entry.has_key('emails'): 0478 numemail+=len(entry['emails']) 0479 if entry.has_key('urls'): 0480 numurl+=len(entry['urls']) 0481 0482 self.progress(numentries, numentries, "Phone book read completed") 0483 self.log("Phone contains "+`count`+" contacts, "+`numcount`+" phone numbers, "+`numemail`+" Emails, "+`numurl`+" URLs") 0484 result['phonebook']=pbook 0485 return pbook 0486 0487 def extractphonebookentry(self, entry, fundamentals): 0488 """Return a phonebook entry in BitPim format""" 0489 res={} 0490 # serials 0491 res['serials']=[ {'sourcetype': self.serialsname, 'serial1': entry.slot, 'serial2': entry.slotdup, 0492 'sourceuniqueid': fundamentals['uniqueserial']} ] 0493 # only one name 0494 res['names']=[ {'full': entry.name} ] 0495 # only one email 0496 if len(entry.email): 0497 res['emails']=[ {'email': entry.email} ] 0498 # only one url 0499 if len(entry.url): 0500 res['urls']=[ {'url': entry.url} ] 0501 # private 0502 res['flags']=[ {'secret': entry.secret } ] 0503 # 7 phone numbers 0504 res['numbers']=[] 0505 numberindex = 0 0506 for type in self.numbertypetab: 0507 if len(entry.numbers[numberindex].number): 0508 res['numbers'].append({'number': entry.numbers[numberindex].number, 'type': type }) 0509 0510 numberindex+=1 0511 return res 0512 0513 def _findmediaindex(self, index, name, pbentryname, type): 0514 if name is None: 0515 return 0 0516 for i in index: 0517 if index[i]['name']==name: 0518 return i 0519 # Not found in index, assume Vision download 0520 pos=name.find('_') 0521 if(pos>=0): 0522 i=int(name[pos+1:]) 0523 return i 0524 0525 return 0 0526 0527 def makeentry(self, entry, dict): 0528 # dict is unused at moment, will be used later to convert string ringtone/wallpaper to numbers?? 0529 # This is stolen from com_lgvx4400 and modified for the Sanyo as 0530 # we start to develop a vague understanding of what it is for. 0531 e=self.protocolclass.phonebookentry() 0532 0533 for k in entry: 0534 # special treatment for lists 0535 if k=='ringtones' or k=='wallpapers' or k=='numbertypes': 0536 continue 0537 if k=='numbers': 0538 for numberindex in range(self.protocolclass.NUMPHONENUMBERS): 0539 enpn=self.protocolclass.phonenumber() 0540 e.numbers.append(enpn) 0541 0542 for i in range(len(entry[k])): 0543 numberindex=entry['numbertypes'][i] 0544 e.numbers[numberindex].number=entry[k][i] 0545 e.numbers[numberindex].number_len=len(e.numbers[numberindex].number) 0546 continue 0547 # everything else we just set 0548 setattr(e,k,entry[k]) 0549 return e 0550 0551 def writewait(self): 0552 """Loop until phone status indicates ready to write""" 0553 for i in range(100): 0554 req=self.protocolclass.statusrequest() 0555 res=self.sendpbcommand(req, self.protocolclass.statusresponse) 0556 # print res.flag0, res.ready, res.flag2, res.flag3 0557 if res.ready==res.readyvalue: 0558 return 0559 time.sleep(0.1) 0560 0561 self.log("Phone did not transfer to ready to write state") 0562 self.log("Waiting a bit longer and trying anyway") 0563 return 0564 0565 def savephonebook(self, data): 0566 # Overwrite the phonebook in the phone with the data. 0567 # As we write the phone book slots out, we need to build up 0568 # the indices in the callerid, ringpic and pbsort buffers. 0569 # We could save some writing by reading the phonebook slots first 0570 # and then only writing those that are different, but all the buffers 0571 # would still need to be written. 0572 # 0573 0574 newphonebook={} 0575 self.mode=self.MODENONE 0576 self.setmode(self.MODEBREW) # see note in getphonebook in com_lgvx4400 for why this is necessary 0577 self.setmode(self.MODEPHONEBOOK) 0578 0579 ### 0580 ### Create Sanyo buffers and Initialize lists 0581 ### 0582 sortstuff=self.protocolclass.pbsortbuffer() 0583 ringpic=self.protocolclass.ringerpicbuffer() 0584 callerid=self.protocolclass.calleridbuffer() 0585 0586 res=self.protocolclass.phonebookentry() 0587 usedefaultnum = 'defaultnum' in res.getfields() 0588 0589 for i in range(self.protocolclass._NUMPBSLOTS): 0590 sortstuff.usedflags.append(0) 0591 sortstuff.firsttypes.append(0) 0592 sortstuff.sortorder.append(0xffff) 0593 sortstuff.sortorder2.append(0xffff) 0594 sortstuff.emails.append(0xffff) 0595 sortstuff.urls.append(0xffff) 0596 ringpic.ringtones.append(0) 0597 ringpic.wallpapers.append(0) 0598 0599 for i in range(self.protocolclass._NUMSPEEDDIALS): 0600 sortstuff.speeddialindex.append(0xffff) 0601 0602 for i in range(self.protocolclass._NUMLONGNUMBERS): 0603 sortstuff.longnumbersindex.append(0xffff) 0604 0605 0606 ### 0607 ### Initialize mappings 0608 ### 0609 namemap=[] 0610 emailmap=[] 0611 urlmap=[] 0612 0613 callerid.numentries=0 0614 0615 pbook=data['phonephonebook'] # Get converted phonebook 0616 self.log("Putting phone into write mode") 0617 req=self.protocolclass.beginendupdaterequest() 0618 req.beginend=1 # Start update 0619 res=self.sendpbcommand(req, self.protocolclass.beginendupdateresponse, writemode=True) 0620 0621 self.writewait() 0622 0623 keys=pbook.keys() 0624 keys.sort() 0625 sortstuff.slotsused=len(keys) 0626 sortstuff.numemail=0 0627 sortstuff.numurl=0 0628 0629 progresscur=0 0630 progressmax=len(keys) 0631 self.log("Writing %d entries to phone" % (len(keys),)) 0632 nlongphonenumbers=0 0633 nonumbercount=0 0634 for ikey in keys: 0635 ii=pbook[ikey] 0636 slot=ii['slot'] # Or should we just use i for the slot 0637 # Will depend on Profile to keep the serial numbers in range 0638 progresscur+=1 0639 self.progress(progresscur, progressmax, "Writing "+ii['name']) 0640 self.log("Writing entry "+`slot`+" - "+ii['name']) 0641 entry=self.makeentry(ii, data) 0642 if not usedefaultnum: 0643 delattr(entry,'defaultnum') 0644 req=self.protocolclass.phonebookslotupdaterequest() 0645 req.entry=entry 0646 res=self.sendpbcommand(req, self.protocolclass.phonebookslotresponse, writemode=True) 0647 # Put entry into newphonebooks 0648 entry=self.extractphonebookentry(entry, data) 0649 entry['ringtones']=[{'ringtone': ii['ringtone'], 'use': 'call'}] 0650 entry['wallpapers']=[{'wallpaper': ii['wallpaper'], 'use': 'call'}] 0651 0652 0653 # Accumulate information in and needed for buffers 0654 sortstuff.usedflags[slot].used=1 0655 if(len(ii['numbers'])): 0656 sortstuff.firsttypes[slot].firsttype=min(ii['numbertypes'])+1 0657 else: 0658 if(len(ii['email'])): 0659 sortstuff.firsttypes[slot].firsttype=8 0660 nonumbercount+=1 0661 elif(len(ii['url'])): 0662 sortstuff.firsttypes[slot].firsttype=9 0663 nonumbercount+=1 0664 else: 0665 sortstuff.firsttypes[slot].firsttype=0 0666 0667 # Fill in Caller ID buffer 0668 # Want to move this out of this loop. Callerid buffer is 500 numbers, but 0669 # can potentially hold 2100 numbers. Would like to preferentially put the 0670 # first number for each name in this buffer. 0671 # If more than 500 numbers are in phone, the phone won't let you add 0672 # any more. So we should probably respect this limit. 0673 for i in range(len(ii['numbers'])): 0674 nindex=ii['numbertypes'][i] 0675 speeddial=ii['speeddials'][i] 0676 code=slot+((nindex+1)<<12) 0677 if(speeddial>=2 and speeddial<=self.protocolclass._NUMSPEEDDIALS+1): 0678 sortstuff.speeddialindex[speeddial-2]=code 0679 for k in range(len(entry['numbers'])): 0680 if(entry['numbers'][k]['type']==nindex): 0681 entry['numbers'][k]['speeddial']=speeddial 0682 break 0683 if(callerid.numentries<callerid.maxentries): 0684 phonenumber=ii['numbers'][i] 0685 cidentry=self.makecidentry(phonenumber,slot,nindex) 0686 callerid.items.append(cidentry) 0687 callerid.numentries+=1 0688 if(len(phonenumber)>self.protocolclass._LONGPHONENUMBERLEN): 0689 if(nlongphonenumbers<self.protocolclass._NUMLONGNUMBERS): 0690 sortstuff.longnumbersindex[nlongphonenumbers].pbslotandtype=code 0691 0692 namemap.append((slot,ii['name'])) 0693 if(len(ii['email'])): 0694 emailmap.append((slot,ii['email'])) 0695 sortstuff.numemail+=1 0696 if(len(ii['url'])): 0697 urlmap.append((slot,ii['url'])) 0698 sortstuff.numurl+=1 0699 # Add ringtone and wallpaper 0700 ringpic.ringtones[slot].ringtone=self._findmediaindex(data['ringtone-index'], ii['ringtone'],ii['name'],'ringtone') 0701 ringpic.wallpapers[slot].wallpaper=self._findmediaindex(data['wallpaper-index'], ii['wallpaper'],ii['name'],'wallpaper') 0702 0703 newphonebook[slot]=entry 0704 0705 sortstuff.slotsused2=len(keys)-nonumbercount 0706 # Sort Names, Emails and Urls for the sort buffer 0707 # The phone sorts case insensitive and puts numbers after the 0708 # letters. 0709 i=0 0710 j=0 0711 sortstuff.pbfirstletters="" 0712 namemap.sort(self.sanyosort) 0713 for (slot, name) in namemap: 0714 sortstuff.sortorder[i].pbslot=slot 0715 if sortstuff.firsttypes[slot].firsttype<=7: 0716 sortstuff.sortorder2[j].pbslot=slot 0717 j+=1 0718 if name: 0719 sortstuff.pbfirstletters+=name[0] 0720 else: 0721 sortstuff.pbfirstletters+=chr(0) 0722 i+=1 0723 0724 i=0 0725 sortstuff.emailfirstletters="" 0726 emailmap.sort(self.sanyosort) 0727 for (slot, email) in emailmap: 0728 sortstuff.emails[i].pbslot=slot 0729 sortstuff.emailfirstletters+=email[0] 0730 i+=1 0731 0732 i=0 0733 sortstuff.urlfirstletters="" 0734 urlmap.sort(self.sanyosort) 0735 for (slot, url) in urlmap: 0736 sortstuff.urls[i].pbslot=slot 0737 sortstuff.urlfirstletters+=url[0] 0738 i+=1 0739 0740 # Now write out the 3 buffers 0741 self.sendsanyobuffer(sortstuff) 0742 0743 if self.protocolclass.HASRINGPICBUF: 0744 self.sendsanyobuffer(ringpic) 0745 0746 self.sendsanyobuffer(callerid) 0747 0748 time.sleep(1.0) 0749 0750 data['phonebook']=newphonebook 0751 del data['phonephonebook'] 0752 data['rebootphone'] = 1 0753 0754 def makecidentry(self, number, slot, nindex): 0755 "Prepare entry for caller ID lookup buffer" 0756 0757 numstripped=re.sub("[^0-9PT#*]", "", number) 0758 numonly=re.sub("^(\\d*).*$", "\\1", numstripped) 0759 0760 cidentry=self.protocolclass.calleridentry() 0761 cidentry.pbslotandtype=slot+((nindex+1)<<12) 0762 cidentry.actualnumberlen=len(numonly) 0763 cidentry.numberfragment=numonly[-10:] 0764 0765 return cidentry 0766 0767 0768 def savewallpapers(self, results, merge): 0769 # print "savewallpapers ",results['wallpaper-index'] 0770 return self.savemedia('wallpapers', 'wallpaper-index', 'images', results, merge) 0771 0772 # Note: Was able to write 6 ringers to phone. If there are ringers 0773 # already on phone, this counts against the 6. Don't know yet if the limit 0774 # is a count limit or a byte limit. 0775 def saveringtones(self, results, merge): 0776 return self.savemedia('ringtone', 'ringtone-index', 'ringers', results, merge) 0777 0778 def savemedia(self, mediakey, mediaindexkey, mediatype, results, merge): 0779 """Actually saves out the media 0780 0781 @param mediakey: key of the media (eg 'wallpapers' or 'ringtones') 0782 Index of media in wallpaper or ringer tab 0783 @param mediaindexkey: index key (eg 'wallpaper-index') 0784 Index of media on the phone 0785 @param results: results dict 0786 """ 0787 0788 wp=results[mediakey].copy() 0789 wpi=results[mediaindexkey].copy() 0790 0791 # remove builtins 0792 for k in wpi.keys(): 0793 if wpi[k]['origin']=='builtin': 0794 del wpi[k] 0795 0796 # Remove camera pictures 0797 for w in wp.keys(): 0798 name=wp[w]['name'] 0799 if wp[w].get("origin", "")=='camera': 0800 self.log("Not transferring camera picture "+name+" to phone") 0801 del wp[w] 0802 else: 0803 for k in wpi.keys(): 0804 if name==wpi[k]['name']: 0805 self.log("Not transferring "+name+" as it is already on the phone") 0806 del wp[w] 0807 break 0808 0809 init={} 0810 init[mediatype]={} 0811 idx=0 0812 for w in wp.keys(): 0813 idx-=1 0814 data=wp[w]['data'] 0815 name=wp[w]['name'] 0816 init[mediatype][idx]={'name': name, 'data': data} 0817 0818 index=init[mediatype] 0819 0820 # write out the content 0821 0822 #### index is dict, key is index number, value is dict 0823 #### value['name'] = filename 0824 #### value['data'] is contents 0825 0826 errors=False 0827 for key in index: 0828 efile=index[key]['name'] 0829 content=index[key]['data'] 0830 if content is None: 0831 continue # in theory we could rewrite .desc file in case index number has changed 0832 # dirname=stripext(efile) 0833 0834 if mediatype=='images': 0835 content = conversions.convertto8bitpng(content,16383) 0836 if not self.writesanyofile(efile, content): 0837 errors=True 0838 0839 if errors: 0840 self.log("One or more files were rejected by the phone, due to") 0841 self.log("invalid file type (only PNG or MIDI are allowed), file") 0842 self.log("size too big, or too many ringers or wallpaper on the phone") 0843 self.log("See Sanyo Error Codes section of the Sanyh Phone Notes") 0844 self.log("in the help for more information") 0845 0846 # Note that we don't write to the camera area 0847 0848 # tidy up - reread indices 0849 # del results[mediakey] # done with it 0850 # reindexfunction(results) 0851 return results 0852 0853 0854 # Error codes 0855 # 0x13 Unknown command 0856 # 0x14 Bad media command 0857 # 0x6d on sendfileterminator 0858 # 0x65 not in sync mode on sendfilename 0859 # 0x6a on sendfilefragment when buffer full, for ringers 6 max? 0860 # 0x6b ?? 0861 # 0x6c on sendfilefragment when full of pictures 11 max? 0862 # 0x6d 0863 # 0x69 on sendfilefragment. Invalid file type. PNG works, jpg, bmp don't 0864 # 0x68 on sendfilefragment. Bad picture size 0865 def writesanyofile(self, name, contents): 0866 start=time.time() 0867 self.log("Writing file '"+name+"' bytes "+`len(contents)`) 0868 desc="Writing "+name 0869 0870 # strip .png and .mid from name because phone will add them 0871 # back on 0872 try: 0873 name=name[:name.index(".mid")] 0874 except: 0875 try: 0876 name=name[:name.index(".png")] 0877 except: 0878 pass 0879 0880 # The newer phones can be put in PC Sync mode with these commands 0881 if hasattr(self.protocolclass,"sanyomediathingyrequest"): 0882 req=self.protocolclass.sanyomediathingyrequest() 0883 req.faset = 0x10 0884 res=self.sendpbcommand(req, self.protocolclass.sanyomediathingyresponse) 0885 time.sleep(10.0) 0886 req.faset = 0x13 0887 res=self.sendpbcommand(req, self.protocolclass.sanyomediathingyresponse) 0888 0889 req=self.protocolclass.sanyosendfilename() 0890 req.filename=name 0891 res=self.sendpbcommand(req, self.protocolclass.sanyosendfileresponse, returnerror=True) 0892 if 'errorcode' in res.getfields(): 0893 if res.errorcode==0x65: 0894 self.alert("Please put your phone into PC Sync Mode", False) 0895 else: 0896 raise SanyoCommandException(res.errorcode) 0897 # Wait about 5 minutes before giving up 0898 waitcount=120 0899 while waitcount>0: 0900 time.sleep(1.0) 0901 res=self.sendpbcommand(req, self.protocolclass.sanyosendfileresponse, returnerror=True) 0902 if not 'errorcode' in res.getfields(): 0903 break 0904 waitcount-=1 0905 if waitcount==0: 0906 raise SanyoCommandException(res.errorcode) 0907 0908 req=self.protocolclass.sanyosendfilesize() 0909 req.filesize=len(contents) 0910 self.sendpbcommand(req, self.protocolclass.sanyosendfileresponse) 0911 0912 req=self.protocolclass.sanyosendfilefragment() 0913 packetsize=req.payloadsize 0914 0915 # time.sleep(1.0) 0916 offset=0 0917 count=0 0918 numblocks=len(contents)/packetsize+1 0919 for offset in range(0, len(contents), packetsize): 0920 count+=1 0921 if count % 5==0: 0922 self.progress(count,numblocks,desc) 0923 req.header.command=offset 0924 req.data=contents[offset:min(offset+packetsize,len(contents))] 0925 # self.sendpbcommand(req, self.protocolclass.sanyosendfileresponse, numsendretry=2) 0926 self.sendpbcommand(req, self.protocolclass.sanyosendfileresponse, writemode=True, returnerror=True) 0927 if 'errorcode' in res.getfields(): 0928 self.log(name+" not written due to error code "+`res.errorcode`) 0929 return False 0930 0931 req=self.protocolclass.sanyosendfileterminator() 0932 try: 0933 res=self.sendpbcommand(req, self.protocolclass.sanyosendfileresponse, writemode=True, returnerror=True) 0934 # The returned value in res.header.faset may mean something 0935 if 'errorcode' in res.getfields(): 0936 self.log(name+" not written due to error code "+`res.errorcode`) 0937 return False 0938 except: 0939 self.log("Exception on writing terminator for file "+name) 0940 self.log("Continuing...") 0941 0942 end=time.time() 0943 if end-start>3: 0944 self.log("Wrote "+`len(contents)`+" bytes at "+`int(len(contents)/(end-start))`+" bytes/second") 0945 return True 0946 0947 0948 def getcalendar(self,result): 0949 # Read the event list from the phone. Proof of principle code. 0950 # For now, join the event name and location into a single event. 0951 # description. 0952 # Todo: 0953 # Read Call Alarms (reminder to call someone) 0954 # Read call history into calendar. 0955 # 0956 calres={} 0957 0958 progressmax=self.protocolclass._NUMEVENTSLOTS+self.protocolclass._NUMCALLALARMSLOTS 0959 req=self.protocolclass.eventrequest() 0960 count=0 0961 0962 try: 0963 reqflag=self.protocolclass.eventslotinuserequest() 0964 except: 0965 reqflag=0 0966 0967 for i in range(0, self.protocolclass._NUMEVENTSLOTS): 0968 self.progress(i,progressmax,"Events") 0969 if reqflag: 0970 reqflag.slot=i 0971 resflag=self.sendpbcommand(reqflag, self.protocolclass.eventslotinuseresponse) 0972 if not resflag.flag: 0973 continue 0974 req.slot = i 0975 res=self.sendpbcommand(req, self.protocolclass.eventresponse) 0976 if not reqflag: 0977 if not res.entry.flag: 0978 continue 0979 self.log("Read calendar event "+`i`+" - "+res.entry.eventname+", alarm ID "+`res.entry.ringtone`) 0980 entry=bpcalendar.CalendarEntry() 0981 #entry.pos=i 0982 entry.changeserial=res.entry.serial 0983 entry.description=res.entry.eventname 0984 entry.location=res.entry.location 0985 starttime=res.entry.start 0986 entry.start=self.decodedate(starttime) 0987 entry.end=self.decodedate(res.entry.end) 0988 repeat=self._calrepeatvalues[res.entry.period] 0989 entry.repeat = self.makerepeat(repeat,entry.start) 0990 0991 if res.entry.alarm==0xffffffff: 0992 entry.alarm=res.entry.alarmdiff/60 0993 else: 0994 alarmtime=res.entry.alarm 0995 entry.alarm=(starttime-alarmtime)/60 0996 ringtone=res.entry.ringtone 0997 print "ringtone=",ringtone 0998 if self.calendar_voicenumber and ringtone == self.calendar_voicenumber: 0999 ringtone=self.phonebook_voicenumber 1000 if ringtone in self.calendar_tonerange: 1001 print "Adjusting ringtone by",-self.calendar_toneoffset 1002 ringtone-=self.calendar_toneoffset 1003 print "ringtone=",ringtone 1004 if ringtone!=self.calendar_defaultringtone: 1005 if result['ringtone-index'].has_key(ringtone): 1006 entry.ringtone=result['ringtone-index'][ringtone]['name'] 1007 1008 entry.snoozedelay=0 1009 calres[entry.id]=entry 1010 count+=1 1011 1012 req=self.protocolclass.callalarmrequest() 1013 for i in range(0, self.protocolclass._NUMCALLALARMSLOTS): 1014 self.progress(self.protocolclass._NUMEVENTSLOTS,progressmax,"Call Alarms") 1015 req.slot=i 1016 res=self.sendpbcommand(req, self.protocolclass.callalarmresponse) 1017 if res.entry.flag and res.entry.date: 1018 self.log("Read call alarm entry "+`i`+" - "+res.entry.phonenum+", alarm ID "+`res.entry.ringtone`) 1019 entry=bpcalendar.CalendarEntry() 1020 #entry.pos=i+self.protocolclass._NUMEVENTSLOTS # Make unique 1021 entry.changeserial=res.entry.serial 1022 entry.description=res.entry.phonenum 1023 starttime=res.entry.date 1024 entry.start=self.decodedate(starttime) 1025 entry.end=entry.start 1026 repeat=self._calrepeatvalues[res.entry.period] 1027 entry.repeat = self.makerepeat(repeat,entry.start) 1028 entry.alarm=0 1029 if res.entry.ringtone!=self.calendar_defaultcaringtone: 1030 entry.ringtone=result['ringtone-index'][res.entry.ringtone]['name'] 1031 entry.snoozedelay=0 1032 calres[entry.id]=entry 1033 count+=1 1034 1035 result['calendar']=calres 1036 return result 1037 1038 def makerepeat(self, repeatword, startdate): 1039 if repeatword is None: 1040 repeat_entry=None 1041 else: 1042 repeat_entry=bpcalendar.RepeatEntry() 1043 if repeatword=='daily': 1044 repeat_entry.repeat_type=repeat_entry.daily 1045 repeat_entry.interval=1 1046 elif repeatword=='monfri': 1047 repeat_entry.repeat_type=repeat_entry.daily 1048 repeat_entry.interval=0 1049 elif repeatword=='weekly': 1050 repeat_entry.repeat_type=repeat_entry.weekly 1051 repeat_entry.interval=1 1052 dow=datetime.date(*startdate[:3]).isoweekday()%7 1053 repeat_entry.dow=1<<dow 1054 elif repeatword=='monthly': 1055 repeat_entry.repeat_type=repeat_entry.monthly 1056 else: 1057 repeat_entry.repeat_type=repeat_entry.yearly 1058 1059 return repeat_entry 1060 1061 def savecalendar(self, dict, merge): 1062 # ::TODO:: obey merge param 1063 # what will be written to the files 1064 # Handle Change Serial better. 1065 # Advance date on repeating entries to after now so that they 1066 # won't all go off when the phone gets turned on. 1067 # Sort by date so that that already happened entries don't get 1068 # loaded if we don't have room 1069 # 1070 cal=dict['calendar'] 1071 newcal={} 1072 keys=cal.keys() 1073 1074 # Value to subtract from mktime results since there is no inverse 1075 # of gmtime 1076 zonedif=time.mktime(time.gmtime(0))-time.mktime(time.localtime(0)) 1077 save_tonerange = xrange(self.calendar_tonerange[0]-self.calendar_toneoffset,self.calendar_tonerange[-1]-self.calendar_toneoffset+1) 1078 1079 try: 1080 reqflag=self.protocolclass.eventslotinuseupdaterequest() 1081 except: 1082 reqflag=0 1083 1084 eventslot=0 1085 callslot=0 1086 progressmax=self.protocolclass._NUMEVENTSLOTS+self.protocolclass._NUMCALLALARMSLOTS 1087 for k in keys: 1088 entry=cal[k] 1089 1090 descloc=entry.description 1091 self.progress(eventslot+callslot, progressmax, "Writing "+descloc) 1092 1093 rp=entry.repeat 1094 if rp is None: 1095 repeat=0 1096 else: 1097 repeatname=None 1098 if rp.repeat_type==rp.daily: 1099 repeatname='daily' 1100 elif rp.repeat_type==rp.weekly: 1101 repeatname='weekly' 1102 elif rp.repeat_type==rp.monthly: 1103 repeatname='monthly' 1104 elif rp.repeat_type==rp.yearly: 1105 repeatname='yearly' 1106 for k,v in self._calrepeatvalues.items(): 1107 if repeatname==v: 1108 repeat=k 1109 break 1110 if repeatname is None: 1111 self.log(descloc+": Repeat type "+`entry.repeat`+" not valid for this phone") 1112 repeat=0 1113 1114 phonenum=re.sub("\-","",descloc) 1115 now=time.mktime(time.localtime(time.time()))-zonedif 1116 if(phonenum.isdigit()): # This is a phone number, use call alarm 1117 self.log("Write calendar call alarm slot "+`callslot`+ " - "+descloc) 1118 e=self.protocolclass.callalarmentry() 1119 e.slot=callslot 1120 e.phonenum=phonenum 1121 e.phonenum_len=len(e.phonenum) 1122 1123 1124 timearray=list(entry.start)+[0,0,0,0] 1125 starttimelocal=time.mktime(timearray)-zonedif 1126 if(starttimelocal<now and repeat==0): 1127 e.flag=2 # In the past 1128 else: 1129 e.flag=1 # In the future 1130 e.date=starttimelocal-self._sanyoepochtounix 1131 e.datedup=e.date 1132 e.phonenumbertype=0 1133 e.phonenumberslot=0 1134 e.name="" # Could get this by reading phone book 1135 # But it would take a lot more time 1136 e.name_len=len(e.name) 1137 #e.ringtone=self.calendar_defaultcaringtone 1138 print "Setting ringtone "+`e.ringtone` 1139 1140 req=self.protocolclass.callalarmupdaterequest() 1141 callslot+=1 1142 respc=self.protocolclass.callalarmresponse 1143 else: # Normal calender event 1144 self.log("Write calendar event slot "+`eventslot`+ " - "+descloc) 1145 e=self.protocolclass.evententry() 1146 e.slot=eventslot 1147 1148 e.eventname=descloc 1149 e.eventname_len=len(e.eventname) 1150 e.location=entry.location 1151 e.location_len=len(e.location) 1152 1153 timearray=list(entry.start)+[0,0,0,0] 1154 starttimelocal=time.mktime(timearray)-zonedif 1155 e.start=starttimelocal-self._sanyoepochtounix 1156 1157 try: 1158 timearray=list(entry.end)+[0,0,0,0] 1159 endtimelocal=time.mktime(timearray)-zonedif 1160 e.end=endtimelocal-self._sanyoepochtounix 1161 except: # If no valid end date, make end 1162 e.end=e.start+60 # one minute later 1163 1164 alarmdiff=entry.alarm 1165 if alarmdiff<0: 1166 alarmdiff=0 1167 alarmdiff=max(alarmdiff,0)*60 1168 e.alarmdiff=alarmdiff 1169 e.alarm=starttimelocal-self._sanyoepochtounix-alarmdiff 1170 1171 if reqflag: 1172 reqflag.slot=eventslot 1173 if(e.alarm+self._sanyoepochtounix<now and repeat==0): 1174 reqflag.flag=4 # In the past 1175 else: 1176 reqflag.flag=1 # In the future 1177 res=self.sendpbcommand(reqflag, self.protocolclass.eventslotinuseresponse) 1178 else: 1179 if(starttimelocal<now and repeat==0): 1180 e.flag=2 # In the past 1181 else: 1182 e.flag=1 # In the future 1183 1184 #timearray=list(entry.get('end', entry['start']))+[0,0,0,0] 1185 #e.end=time.mktime(timearray)-self._sanyoepochtounix-zonedif 1186 try: 1187 timearray=list(entry.end)+[0,0,0,0] 1188 endtimelocal=time.mktime(timearray)-zonedif 1189 e.end=endtimelocal-self._sanyoepochtounix 1190 except: # If no valid end date, make end 1191 e.end=e.start+60 # one minute later 1192 1193 ringtoneindex=self._findmediaindex(dict['ringtone-index'],entry.ringtone,'Calendar','ringtone') 1194 if ringtoneindex==0: 1195 e.ringtone=self.calendar_defaultringtone 1196 else: 1197 e.ringtone=ringtoneindex 1198 if e.ringtone in save_tonerange: 1199 e.ringtone+=self.calendar_toneoffset 1200 if self.calendar_voicenumber and e.ringtone==self.phonebook_voicenumber: 1201 e.ringtone=self.calendar_voicenumber 1202 # Use default ringtone for now 1203 #e.ringtone=self.calendar_defaultringtone 1204 print "Setting ringtone "+`e.ringtone` 1205 1206 # What we should do is first find largest changeserial, and then increment 1207 # whenever we have one that is undefined or zero. 1208 1209 req=self.protocolclass.eventupdaterequest() 1210 eventslot+=1 1211 respc=self.protocolclass.eventresponse 1212 1213 e.period=repeat 1214 e.dom=entry.start[2] 1215 if entry.id>=0 and entry.id<256: 1216 e.serial=entry.id 1217 else: 1218 e.serial=0 1219 req.entry=e 1220 res=self.sendpbcommand(req, respc, writemode=True) 1221 1222 1223 # Blank out unused slots 1224 1225 if reqflag: 1226 req=reqflag 1227 req.flag=0 1228 else: 1229 e=self.protocolclass.evententry() 1230 e.flag=0 # Unused slot 1231 e.eventname="" 1232 e.eventname_len=0 1233 e.location="" 1234 e.location_len=0 1235 e.start=0 1236 e.end=0 1237 e.period=0 1238 e.dom=0 1239 e.ringtone=0 1240 e.alarm=0 1241 e.alarmdiff=0 1242 req=self.protocolclass.eventupdaterequest() 1243 req.entry=e 1244 1245 for eventslot in range(eventslot,self.protocolclass._NUMEVENTSLOTS): 1246 self.progress(eventslot+callslot, progressmax, "Writing unused") 1247 self.log("Write calendar event slot "+`eventslot`+ " - Unused") 1248 if reqflag: 1249 req.slot=eventslot 1250 else: 1251 req.entry.slot=eventslot 1252 res=self.sendpbcommand(req, self.protocolclass.eventresponse, writemode=True) 1253 1254 e=self.protocolclass.callalarmentry() 1255 e.flag=0 # Unused slot 1256 e.name="" 1257 e.name_len=0 1258 e.phonenum="" 1259 e.phonenum_len=0 1260 e.date=0 1261 e.datedup=0 1262 e.period=0 1263 e.dom=0 1264 e.ringtone=0 1265 e.phonenumbertype=0 1266 e.phonenumberslot=0 1267 req=self.protocolclass.callalarmupdaterequest() 1268 req.entry=e 1269 for callslot in range(callslot,self.protocolclass._NUMCALLALARMSLOTS): 1270 self.progress(eventslot+callslot, progressmax, "Writing unused") 1271 self.log("Write calendar call alarm slot "+`callslot`+ " - Unused") 1272 req.entry.slot=callslot 1273 res=self.sendpbcommand(req, self.protocolclass.callalarmresponse, writemode=True) 1274 1275 self.progress(progressmax, progressmax, "Calendar write done") 1276 1277 dict['rebootphone'] = True 1278 return dict 1279 1280 def getcallhistory(self, result): 1281 res={} 1282 self._readhistory(self.protocolclass.OUTGOING,'Outgoing',res) 1283 self._readhistory(self.protocolclass.MISSED,'Missed',res) 1284 self._readhistory(self.protocolclass.INCOMING,'Incoming',res) 1285 result['call_history']=res 1286 return result 1287 1288 def _readhistory(self, type, folder, res): 1289 req=self.protocolclass.historyrequest() 1290 req.type=type 1291 for slot in range(self.protocolclass.NUMCALLHISTORY): 1292 req.slot=slot 1293 call=self.sendpbcommand(req, self.protocolclass.historyresponse) 1294 if call.entry.phonenum=='' and call.entry.name=='': 1295 continue 1296 entry=call_history.CallHistoryEntry() 1297 entry.folder=folder 1298 entry.name=call.entry.name 1299 entry.number=call.entry.phonenum 1300 entry.datetime=((call.entry.date)) 1301 res[entry.id]=entry 1302 1303 def getphoneinfo(self, phone_info): 1304 self.log('Getting Phone Info') 1305 try: 1306 phone_info.append('ESN:', self.get_esn()) 1307 phone_info.append('Manufacturer:', 'Sanyo') 1308 req=self.protocolclass.lockcoderequest() 1309 try: 1310 res=self.sendpbcommand(req, self.protocolclass.lockcoderesponse) 1311 phone_info.append('Lock Code:', res.lockcode) 1312 except: 1313 pass 1314 1315 req=self.protocolclass.sanyofirmwarerequest() 1316 res=self.sendpbcommand(req, self.protocolclass.sanyofirmwareresponse) 1317 phone_info.append('Firmware Version:',res.firmware) 1318 if 'prl' in res.getfields(): 1319 phone_info.append('PRL:', res.prl) 1320 if 'phonemodel' in res.getfields(): 1321 phone_info.append('Model:', res.phonemodel) 1322 else: 1323 phone_info.append('Model:', self.desc) 1324 req=self.protocolclass.phonenumberrequest() 1325 res=self.sendpbcommand(req, self.protocolclass.phonenumberresponse) 1326 phone_info.append('Phone Number:', res.myphonenumber) 1327 1328 req=self.protocolclass.reconditionedrequest() 1329 res=self.sendpbcommand(req, self.protocolclass.reconditionedresponse) 1330 if res.reconditioned: 1331 recon='Yes' 1332 else: 1333 recon='No' 1334 phone_info.append('Reconditioned:',recon) 1335 except: 1336 pass 1337 1338 return 1339 1340 def decodedate(self,val): 1341 """Unpack 32 bit value into date/time 1342 1343 @rtype: tuple 1344 @return: (year, month, day, hour, minute) 1345 """ 1346 return list(time.gmtime(val+self._sanyoepochtounix)[:5]) 1347 1348 _calrepeatvalues={ 1349 0: None, 1350 1: 'daily', 1351 2: 'weekly', 1352 3: 'monthly', 1353 4: 'yearly' 1354 } 1355 1356 1357 def phonize(str): 1358 """Convert the phone number into something the phone understands 1359 1360 All digits, P, T, * and # are kept, everything else is removed""" 1361 # Note: when looking at phone numbers on the phone, you will see 1362 # "H" instead of "P". However, phone saves this internally as "P". 1363 return re.sub("[^0-9PT#*]", "", str) 1364 1365 class Profile(com_phone.Profile): 1366 serialsname='sanyo' 1367 1368 WALLPAPER_WIDTH=120 1369 WALLPAPER_HEIGHT=128 1370 OVERSIZE_PERCENTAGE=100 1371 1372 MAX_WALLPAPER_BASENAME_LENGTH=19 1373 WALLPAPER_FILENAME_CHARS="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 .`~!@#$%^&()-_=+[{]};\'" 1374 WALLPAPER_CONVERT_FORMAT="png" 1375 1376 MAX_RINGTONE_BASENAME_LENGTH=19 1377 RINGTONE_FILENAME_CHARS="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 .`~!@#$%^&()-_=+[{]};\'" 1378 1379 # which usb ids correspond to us 1380 usbids=( ( 0x0474, 0x0701, 1), # VID=Sanyo, PID=4900 internal USB interface 1381 ) 1382 # which device classes we are. 1383 deviceclasses=("modem",) 1384 1385 BP_Calendar_Version=3 1386 1387 def __init__(self): 1388 self.numbertypetab=numbertypetab 1389 com_phone.Profile.__init__(self) 1390 1391 # which sync types we deal with 1392 _supportedsyncs=( 1393 ('phonebook', 'read', None), # all phonebook reading 1394 ('calendar', 'read', None), # all calendar reading 1395 ('phonebook', 'write', 'OVERWRITE'), # only overwriting phonebook 1396 ('calendar', 'write', 'OVERWRITE'), # only overwriting calendar 1397 ('wallpaper', 'write', 'MERGE'), 1398 ('ringtone', 'write', 'MERGE'), 1399 ('call_history', 'read', None),# all call history list reading 1400 ('sms', 'read', None), 1401 ) 1402 ### 1403 1404 # Processes phone book for writing to Sanyo. But does not leave phone book 1405 # in a bitpim compatible format. Need to understand exactly what bitpim 1406 # is expecting the method to do. 1407 1408 def convertphonebooktophone(self, helper, data): 1409 "Converts the data to what will be used by the phone" 1410 results={} 1411 1412 slotsused={} 1413 for pbentry in data['phonebook']: 1414 entry=data['phonebook'][pbentry] 1415 serial1=helper.getserial(entry.get('serials', []), self.serialsname, data['uniqueserial'], 'serial1', -1) 1416 if(serial1 >= 0 and serial1 < self.protocolclass._NUMPBSLOTS): 1417 slotsused[serial1]=1 1418 1419 lastunused=0 # One more than last unused slot 1420 1421 for pbentry in data['phonebook']: 1422 e={} # entry out 1423 entry=data['phonebook'][pbentry] # entry in 1424 try: 1425 try: 1426 e['name']=helper.getfullname(entry.get('names', []),1,1,16)[0] 1427 except: 1428 e['name']='' 1429 e['name_len']=len(e['name']) 1430 1431 serial1=helper.getserial(entry.get('serials', []), self.serialsname, data['uniqueserial'], 'serial1', -1) 1432 1433 if(serial1 >= 0 and serial1 < self.protocolclass._NUMPBSLOTS): 1434 e['slot']=serial1 1435 else: # A new entry. Must find unused slot 1436 while(slotsused.has_key(lastunused)): 1437 lastunused+=1 1438 if(lastunused >= self.protocolclass._NUMPBSLOTS): 1439 raise helper.ConversionFailed() 1440 e['slot']=lastunused 1441 slotsused[lastunused]=1 1442 1443 e['slotdup']=e['slot'] 1444 1445 e['email']=helper.makeone(helper.getemails(entry.get('emails', []),0,1,self.protocolclass._MAXEMAILLEN), "") 1446 e['email_len']=len(e['email']) 1447 1448 e['url']=helper.makeone(helper.geturls(entry.get('urls', []), 0,1,self.protocolclass._MAXEMAILLEN), "") 1449 e['url_len']=len(e['url']) 1450 # Could put memo in email or url 1451 1452 numbers=helper.getnumbers(entry.get('numbers', []),0,7) 1453 e['numbertypes']=[] 1454 e['numbers']=[] 1455 e['speeddials']=[] 1456 unusednumbers=[] # Hold duplicate types here 1457 typesused={} 1458 defaultnum=0 1459 for num in numbers: 1460 typename=num['type'] 1461 if(typesused.has_key(typename)): 1462 unusednumbers.append(num) 1463 continue 1464 typesused[typename]=1 1465 for typenum,tnsearch in enumerate(self.numbertypetab): 1466 if typename==tnsearch: 1467 if defaultnum==0: 1468 defaultnum=typenum+1 1469 number=phonize(num['number']) 1470 if len(number)>self.protocolclass._MAXNUMBERLEN: # get this number from somewhere sensible 1471 # :: TODO:: number is too long and we have to either truncate it or ignore it? 1472 number=number[:self.protocolclass._MAXNUMBERLEN] 1473 e['numbers'].append(number) 1474 if(num.has_key('speeddial')): 1475 e['speeddials'].append(num['speeddial']) 1476 else: 1477 e['speeddials'].append(-1) 1478 1479 e['numbertypes'].append(typenum) 1480 1481 break 1482 1483 # Now stick the unused numbers in unused types 1484 trytype=len(self.numbertypetab) 1485 for num in unusednumbers: 1486 while trytype>0: 1487 trytype-=1 1488 if not typesused.has_key(self.numbertypetab[trytype]): 1489 break 1490 else: 1491 break 1492 if defaultnum==0: 1493 defaultnum=trytype+1 1494 number=phonize(num['number']) 1495 if len(number)>self.protocolclass._MAXNUMBERLEN: # get this number from somewhere sensible 1496 # :: TODO:: number is too long and we have to either truncate it or ignore it? 1497 number=number[:self.protocolclass._MAXNUMBERLEN] 1498 e['numbers'].append(number) 1499 e['numbertypes'].append(trytype) 1500 if(num.has_key('speeddial')): 1501 e['speeddials'].append(num['speeddial']) 1502 else: 1503 e['speeddials'].append(-1) 1504 1505 if defaultnum==0: 1506 if e['url_len'] > 0: 1507 defaultnum=8 1508 elif e['email_len'] > 0: 1509 defaultnum=9 1510 1511 e['ringtone']=helper.getringtone(entry.get('ringtones', []), 'call', None) 1512 e['wallpaper']=helper.getwallpaper(entry.get('wallpapers', []), 'call', None) 1513 1514 e['secret']=helper.getflag(entry.get('flags', []), 'secret', False) 1515 e['defaultnum']=defaultnum 1516 1517 results[pbentry]=e 1518 1519 except helper.ConversionFailed: 1520 #self.log("No Free Slot for "+e['name']) 1521 print "No Free Slot for "+e['name'] 1522 continue 1523 1524 data['phonephonebook']=results 1525 return data 1526 1527 1528 class Phone(SanyoPhonebook,com_phone.Phone,com_brew.BrewProtocol): 1529 "Talk to a Sanyo Sprint Phone such as SCP-4900, SCP-5300, or SCP-8100" 1530 desc="Sanyo" 1531 helpid=helpids.ID_PHONE_SANYOOTHERS 1532 imagelocations=() 1533 1534 ringtonelocations=() 1535 1536 builtinimages=() 1537 1538 builtinringtones=() 1539 1540 def __init__(self, logtarget, commport): 1541 "Call all the contructors and sets initial modes" 1542 com_phone.Phone.__init__(self, logtarget, commport) 1543 com_brew.BrewProtocol.__init__(self) 1544 SanyoPhonebook.__init__(self) 1545 self.log("Attempting to contact phone") 1546 self.mode=self.MODENONE 1547 1548 getwallpapers=None 1549 getringtones=None 1550 1551
Generated by PyXR 0.9.4